Add SDK 29 sources.

Test: N/A
Change-Id: Iedb7a31029e003928eb16f7e69ed147e72bb6235
diff --git a/android/accessibilityservice/AccessibilityButtonController.java b/android/accessibilityservice/AccessibilityButtonController.java
new file mode 100644
index 0000000..af5af9c
--- /dev/null
+++ b/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 com.android.internal.util.Preconditions;
+
+/**
+ * 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) {
+        Preconditions.checkNotNull(callback);
+        Preconditions.checkNotNull(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) {
+        Preconditions.checkNotNull(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/accessibilityservice/AccessibilityService.java b/android/accessibilityservice/AccessibilityService.java
new file mode 100644
index 0000000..cebe6e1
--- /dev/null
+++ b/android/accessibilityservice/AccessibilityService.java
@@ -0,0 +1,1986 @@
+/*
+ * 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 android.accessibilityservice.GestureDescription.MotionEventGenerator;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.UnsupportedAppUsage;
+import android.app.Service;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Region;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+import android.view.WindowManagerImpl;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityInteractionClient;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.List;
+
+/**
+ * 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>
+ *     <ol>
+ *         Specify that it handles the "android.accessibilityservice.AccessibilityService"
+ *         {@link android.content.Intent}.
+ *     </ol>
+ *     <ol>
+ *         Request the {@link android.Manifest.permission#BIND_ACCESSIBILITY_SERVICE} permission to
+ *         ensure that only the system can bind to it.
+ *     </ol>
+ * </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>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_AUDIBLE}</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 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 an down and left gesture on the touch screen.
+     */
+    public static final int GESTURE_SWIPE_DOWN_AND_LEFT = 15;
+
+    /**
+     * The user has performed an down and right gesture on the touch screen.
+     */
+    public static final int GESTURE_SWIPE_DOWN_AND_RIGHT = 16;
+
+    /**
+     * 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 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
+     */
+    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;
+
+    private static final String LOG_TAG = "AccessibilityService";
+
+    /**
+     * Interface used by IAccessibilityServiceWrapper 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);
+        boolean onGesture(int gestureId);
+        boolean onKeyEvent(KeyEvent event);
+        /** Magnification changed callbacks for different displays */
+        void onMagnificationChanged(int displayId, @NonNull Region region,
+                float scale, float centerX, float centerY);
+        void onSoftKeyboardShowModeChanged(int showMode);
+        void onPerformGestureResult(int sequence, boolean completedSuccessfully);
+        void onFingerprintCapturingGesturesChanged(boolean active);
+        void onFingerprintGesture(int gesture);
+        void onAccessibilityButtonClicked();
+        void onAccessibilityButtonAvailabilityChanged(boolean available);
+    }
+
+    /**
+     * 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;
+
+    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);
+    private SoftKeyboardController mSoftKeyboardController;
+    private AccessibilityButtonController mAccessibilityButtonController;
+
+    private int mGestureStatusCallbackSequence;
+
+    private SparseArray<GestureResultCallbackInfo> mGestureStatusCallbackInfos;
+
+    private final Object mLock = new Object();
+
+    private FingerprintGestureController mFingerprintGestureController;
+
+    /**
+     * 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();
+            }
+        }
+        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();
+    }
+
+    /**
+     * 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 the system when the user performs a specific gesture on the
+     * touch screen.
+     *
+     * <strong>Note:</strong> To receive gestures an accessibility service must
+     * request that the device is in touch exploration mode by setting the
+     * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+     * flag.
+     *
+     * @param gestureId The unique id of the performed gesture.
+     *
+     * @return Whether the gesture was handled.
+     *
+     * @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
+     */
+    protected boolean onGesture(int gestureId) {
+        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;
+    }
+
+    /**
+     * Gets the windows on the screen. 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().getWindows(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.
+     * <p>
+     * The currently active window is defined as the window that most recently fired one
+     * of the following events:
+     * {@link AccessibilityEvent#TYPE_WINDOW_STATE_CHANGED},
+     * {@link AccessibilityEvent#TYPE_VIEW_HOVER_ENTER},
+     * {@link AccessibilityEvent#TYPE_VIEW_HOVER_EXIT}.
+     * In other words, the last window shown that also has input focus.
+     * </p>
+     * <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.
+     */
+    public AccessibilityNodeInfo getRootInActiveWindow() {
+        return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId);
+    }
+
+    /**
+     * 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().getConnection(mConnectionId);
+        if (connection != null) {
+            try {
+                connection.disableSelf();
+            } catch (RemoteException re) {
+                throw new RuntimeException(re);
+            }
+        }
+    }
+
+    /**
+     * 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().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>
+     *
+     * @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().getConnection(
+                        mConnectionId);
+        if (connection == null) {
+            return false;
+        }
+        List<GestureDescription.GestureStep> steps =
+                MotionEventGenerator.getGestureStepsFromGestureDescription(gesture, 100);
+        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.sendGesture(mGestureStatusCallbackSequence,
+                        new ParceledListSlice<>(steps));
+            }
+        } catch (RemoteException re) {
+            throw new RuntimeException(re);
+        }
+        return true;
+    }
+
+    void onPerformGestureResult(int sequence, final boolean completedSuccessfully) {
+        if (mGestureStatusCallbackInfos == null) {
+            return;
+        }
+        GestureResultCallbackInfo callbackInfo;
+        synchronized (mLock) {
+            callbackInfo = mGestureStatusCallbackInfos.get(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, float scale,
+            float centerX, float centerY) {
+        MagnificationController controller;
+        synchronized (mLock) {
+            controller = mMagnificationControllers.get(displayId);
+        }
+        if (controller != null) {
+            controller.dispatchMagnificationChanged(region, scale, centerX, centerY);
+        }
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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().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 float scale,
+                final float centerX, final float centerY) {
+            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(new Runnable() {
+                        @Override
+                        public void run() {
+                            listener.onMagnificationChanged(MagnificationController.this,
+                                    region, scale, centerX, centerY);
+                        }
+                    });
+                } else {
+                    // We're already on the main thread, just run the listener.
+                    listener.onMagnificationChanged(this, region, scale, centerX, centerY);
+                }
+            }
+        }
+
+        /**
+         * 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}.
+         *
+         * @return the current magnification scale
+         */
+        public float getScale() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().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}.
+         *
+         * @return the unscaled screen-relative X coordinate of the center of
+         *         the magnified region
+         */
+        public float getCenterX() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().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}.
+         *
+         * @return the unscaled screen-relative Y coordinate of the center of
+         *         the magnified region
+         */
+        public float getCenterY() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().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.
+         *
+         * @return the region of the screen currently active for magnification, or an empty region
+         * if magnification is not active.
+         */
+        @NonNull
+        public Region getMagnificationRegion() {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().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();
+        }
+
+        /**
+         * 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}.
+         *
+         * @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().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;
+        }
+
+        /**
+         * 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}.
+         *
+         * @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
+         */
+        public boolean setScale(float scale, boolean animate) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.setMagnificationScaleAndCenter(mDisplayId,
+                            scale, Float.NaN, Float.NaN, 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}.
+         *
+         * @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
+         */
+        public boolean setCenter(float centerX, float centerY, boolean animate) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.setMagnificationScaleAndCenter(mDisplayId,
+                            Float.NaN, centerX, centerY, 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.
+             *
+             * @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
+             */
+            void onMagnificationChanged(@NonNull MagnificationController controller,
+                    @NonNull Region region, float scale, float centerX, float centerY);
+        }
+    }
+
+    /**
+     * 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;
+        }
+    }
+
+    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;
+
+        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().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().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().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);
+        }
+    }
+
+    /**
+     * 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() {
+        synchronized (mLock) {
+            if (mAccessibilityButtonController == null) {
+                mAccessibilityButtonController = new AccessibilityButtonController(
+                        AccessibilityInteractionClient.getInstance().getConnection(mConnectionId));
+            }
+            return mAccessibilityButtonController;
+        }
+    }
+
+    private void onAccessibilityButtonClicked() {
+        getAccessibilityButtonController().dispatchAccessibilityButtonClicked();
+    }
+
+    private void onAccessibilityButtonAvailabilityChanged(boolean available) {
+        getAccessibilityButtonController().dispatchAccessibilityButtonAvailabilityChanged(
+                available);
+    }
+
+    /**
+     * 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.
+     *
+     * @param action The action to perform.
+     * @return Whether the action was successfully performed.
+     *
+     * @see #GLOBAL_ACTION_BACK
+     * @see #GLOBAL_ACTION_HOME
+     * @see #GLOBAL_ACTION_NOTIFICATIONS
+     * @see #GLOBAL_ACTION_RECENTS
+     */
+    public final boolean performGlobalAction(int action) {
+        IAccessibilityServiceConnection connection =
+            AccessibilityInteractionClient.getInstance().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>
+     *
+     * @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().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().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;
+        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().getConnection(mConnectionId);
+        if (mInfo != null && connection != null) {
+            try {
+                connection.setServiceInfo(mInfo);
+                mInfo = null;
+                AccessibilityInteractionClient.getInstance().clearCache();
+            } 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);
+            }
+            return mWindowManager;
+        }
+        return super.getSystemService(name);
+    }
+
+    /**
+     * Implement to return the implementation of the internal accessibility
+     * service interface.
+     */
+    @Override
+    public final IBinder onBind(Intent intent) {
+        return new IAccessibilityServiceClientWrapper(this, getMainLooper(), 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.
+                final WindowManagerImpl wm = (WindowManagerImpl) getSystemService(WINDOW_SERVICE);
+                wm.setDefaultToken(windowToken);
+            }
+
+            @Override
+            public boolean onGesture(int gestureId) {
+                return AccessibilityService.this.onGesture(gestureId);
+            }
+
+            @Override
+            public boolean onKeyEvent(KeyEvent event) {
+                return AccessibilityService.this.onKeyEvent(event);
+            }
+
+            @Override
+            public void onMagnificationChanged(int displayId, @NonNull Region region,
+                    float scale, float centerX, float centerY) {
+                AccessibilityService.this.onMagnificationChanged(displayId, region, scale,
+                        centerX, centerY);
+            }
+
+            @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() {
+                AccessibilityService.this.onAccessibilityButtonClicked();
+            }
+
+            @Override
+            public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+                AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available);
+            }
+        });
+    }
+
+    /**
+     * 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
+            implements HandlerCaller.Callback {
+        private static final int DO_INIT = 1;
+        private static final int DO_ON_INTERRUPT = 2;
+        private static final int DO_ON_ACCESSIBILITY_EVENT = 3;
+        private static final int DO_ON_GESTURE = 4;
+        private static final int DO_CLEAR_ACCESSIBILITY_CACHE = 5;
+        private static final int DO_ON_KEY_EVENT = 6;
+        private static final int DO_ON_MAGNIFICATION_CHANGED = 7;
+        private static final int DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED = 8;
+        private static final int DO_GESTURE_COMPLETE = 9;
+        private static final int DO_ON_FINGERPRINT_ACTIVE_CHANGED = 10;
+        private static final int DO_ON_FINGERPRINT_GESTURE = 11;
+        private static final int DO_ACCESSIBILITY_BUTTON_CLICKED = 12;
+        private static final int DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED = 13;
+
+        private final HandlerCaller mCaller;
+
+        private final Callbacks mCallback;
+
+        private int mConnectionId = AccessibilityInteractionClient.NO_ID;
+
+        public IAccessibilityServiceClientWrapper(Context context, Looper looper,
+                Callbacks callback) {
+            mCallback = callback;
+            mCaller = new HandlerCaller(context, looper, this, true /*asyncHandler*/);
+        }
+
+        public void init(IAccessibilityServiceConnection connection, int connectionId,
+                IBinder windowToken) {
+            Message message = mCaller.obtainMessageIOO(DO_INIT, connectionId,
+                    connection, windowToken);
+            mCaller.sendMessage(message);
+        }
+
+        public void onInterrupt() {
+            Message message = mCaller.obtainMessage(DO_ON_INTERRUPT);
+            mCaller.sendMessage(message);
+        }
+
+        public void onAccessibilityEvent(AccessibilityEvent event, boolean serviceWantsEvent) {
+            Message message = mCaller.obtainMessageBO(
+                    DO_ON_ACCESSIBILITY_EVENT, serviceWantsEvent, event);
+            mCaller.sendMessage(message);
+        }
+
+        public void onGesture(int gestureId) {
+            Message message = mCaller.obtainMessageI(DO_ON_GESTURE, gestureId);
+            mCaller.sendMessage(message);
+        }
+
+        public void clearAccessibilityCache() {
+            Message message = mCaller.obtainMessage(DO_CLEAR_ACCESSIBILITY_CACHE);
+            mCaller.sendMessage(message);
+        }
+
+        @Override
+        public void onKeyEvent(KeyEvent event, int sequence) {
+            Message message = mCaller.obtainMessageIO(DO_ON_KEY_EVENT, sequence, event);
+            mCaller.sendMessage(message);
+        }
+
+        /** Magnification changed callbacks for different displays */
+        public void onMagnificationChanged(int displayId, @NonNull Region region,
+                float scale, float centerX, float centerY) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.arg1 = region;
+            args.arg2 = scale;
+            args.arg3 = centerX;
+            args.arg4 = centerY;
+            args.argi1 = displayId;
+
+            final Message message = mCaller.obtainMessageO(DO_ON_MAGNIFICATION_CHANGED, args);
+            mCaller.sendMessage(message);
+        }
+
+        public void onSoftKeyboardShowModeChanged(int showMode) {
+          final Message message =
+                  mCaller.obtainMessageI(DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED, showMode);
+          mCaller.sendMessage(message);
+        }
+
+        public void onPerformGestureResult(int sequence, boolean successfully) {
+            Message message = mCaller.obtainMessageII(DO_GESTURE_COMPLETE, sequence,
+                    successfully ? 1 : 0);
+            mCaller.sendMessage(message);
+        }
+
+        public void onFingerprintCapturingGesturesChanged(boolean active) {
+            mCaller.sendMessage(mCaller.obtainMessageI(
+                    DO_ON_FINGERPRINT_ACTIVE_CHANGED, active ? 1 : 0));
+        }
+
+        public void onFingerprintGesture(int gesture) {
+            mCaller.sendMessage(mCaller.obtainMessageI(DO_ON_FINGERPRINT_GESTURE, gesture));
+        }
+
+        public void onAccessibilityButtonClicked() {
+            final Message message = mCaller.obtainMessage(DO_ACCESSIBILITY_BUTTON_CLICKED);
+            mCaller.sendMessage(message);
+        }
+
+        public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+            final Message message = mCaller.obtainMessageI(
+                    DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED, (available ? 1 : 0));
+            mCaller.sendMessage(message);
+        }
+
+        @Override
+        public void executeMessage(Message message) {
+            switch (message.what) {
+                case DO_ON_ACCESSIBILITY_EVENT: {
+                    AccessibilityEvent event = (AccessibilityEvent) message.obj;
+                    boolean serviceWantsEvent = message.arg1 != 0;
+                    if (event != null) {
+                        // Send the event to AccessibilityCache via AccessibilityInteractionClient
+                        AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
+                        if (serviceWantsEvent
+                                && (mConnectionId != AccessibilityInteractionClient.NO_ID)) {
+                            // Send the event to AccessibilityService
+                            mCallback.onAccessibilityEvent(event);
+                        }
+                        // Make sure the event is recycled.
+                        try {
+                            event.recycle();
+                        } catch (IllegalStateException ise) {
+                            /* ignore - best effort */
+                        }
+                    }
+                } return;
+
+                case DO_ON_INTERRUPT: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.onInterrupt();
+                    }
+                } return;
+
+                case DO_INIT: {
+                    mConnectionId = message.arg1;
+                    SomeArgs args = (SomeArgs) message.obj;
+                    IAccessibilityServiceConnection connection =
+                            (IAccessibilityServiceConnection) args.arg1;
+                    IBinder windowToken = (IBinder) args.arg2;
+                    args.recycle();
+                    if (connection != null) {
+                        AccessibilityInteractionClient.getInstance().addConnection(mConnectionId,
+                                connection);
+                        mCallback.init(mConnectionId, windowToken);
+                        mCallback.onServiceConnected();
+                    } else {
+                        AccessibilityInteractionClient.getInstance().removeConnection(
+                                mConnectionId);
+                        mConnectionId = AccessibilityInteractionClient.NO_ID;
+                        AccessibilityInteractionClient.getInstance().clearCache();
+                        mCallback.init(AccessibilityInteractionClient.NO_ID, null);
+                    }
+                } return;
+
+                case DO_ON_GESTURE: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final int gestureId = message.arg1;
+                        mCallback.onGesture(gestureId);
+                    }
+                } return;
+
+                case DO_CLEAR_ACCESSIBILITY_CACHE: {
+                    AccessibilityInteractionClient.getInstance().clearCache();
+                } return;
+
+                case DO_ON_KEY_EVENT: {
+                    KeyEvent event = (KeyEvent) message.obj;
+                    try {
+                        IAccessibilityServiceConnection connection = AccessibilityInteractionClient
+                                .getInstance().getConnection(mConnectionId);
+                        if (connection != null) {
+                            final boolean result = mCallback.onKeyEvent(event);
+                            final int sequence = message.arg1;
+                            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;
+
+                case DO_ON_MAGNIFICATION_CHANGED: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final SomeArgs args = (SomeArgs) message.obj;
+                        final Region region = (Region) args.arg1;
+                        final float scale = (float) args.arg2;
+                        final float centerX = (float) args.arg3;
+                        final float centerY = (float) args.arg4;
+                        final int displayId = args.argi1;
+                        args.recycle();
+                        mCallback.onMagnificationChanged(displayId, region, scale,
+                                centerX, centerY);
+                    }
+                } return;
+
+                case DO_ON_SOFT_KEYBOARD_SHOW_MODE_CHANGED: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final int showMode = (int) message.arg1;
+                        mCallback.onSoftKeyboardShowModeChanged(showMode);
+                    }
+                } return;
+
+                case DO_GESTURE_COMPLETE: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final boolean successfully = message.arg2 == 1;
+                        mCallback.onPerformGestureResult(message.arg1, successfully);
+                    }
+                } return;
+                case DO_ON_FINGERPRINT_ACTIVE_CHANGED: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.onFingerprintCapturingGesturesChanged(message.arg1 == 1);
+                    }
+                } return;
+                case DO_ON_FINGERPRINT_GESTURE: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.onFingerprintGesture(message.arg1);
+                    }
+                } return;
+
+                case (DO_ACCESSIBILITY_BUTTON_CLICKED): {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.onAccessibilityButtonClicked();
+                    }
+                } return;
+
+                case (DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED): {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final boolean available = (message.arg1 != 0);
+                        mCallback.onAccessibilityButtonAvailabilityChanged(available);
+                    }
+                } return;
+
+                default :
+                    Log.w(LOG_TAG, "Unknown message type " + message.what);
+            }
+        }
+    }
+
+    /**
+     * 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;
+        }
+    }
+}
diff --git a/android/accessibilityservice/AccessibilityServiceInfo.java b/android/accessibilityservice/AccessibilityServiceInfo.java
new file mode 100644
index 0000000..cf24b8e
--- /dev/null
+++ b/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -0,0 +1,1240 @@
+/*
+ * 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.content.pm.PackageManager.FEATURE_FINGERPRINT;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.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.hardware.fingerprint.FingerprintManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.R;
+
+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_canRequestEnhancedWebAccessibility
+ * @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_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_nonInteractiveUiTimeout
+ * @attr ref android.R.styleable#AccessibilityService_interactiveUiTimeout
+ * @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 = 0x00000001;
+
+    /**
+     * 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 = 0x00000002;
+
+    /**
+     * @deprecated No longer used
+     */
+    public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000004;
+
+    /**
+     * 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 = 0x00000008;
+
+    /**
+     * Capability: This accessibility service can control display magnification.
+     * @see android.R.styleable#AccessibilityService_canControlMagnification
+     */
+    public static final int CAPABILITY_CAN_CONTROL_MAGNIFICATION = 0x00000010;
+
+    /**
+     * Capability: This accessibility service can perform gestures.
+     * @see android.R.styleable#AccessibilityService_canPerformGestures
+     */
+    public static final int CAPABILITY_CAN_PERFORM_GESTURES = 0x00000020;
+
+    /**
+     * 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 = 0x00000040;
+
+    private static SparseArray<CapabilityInfo> sAvailableCapabilityInfos;
+
+    /**
+     * Denotes spoken feedback.
+     */
+    public static final int FEEDBACK_SPOKEN = 0x0000001;
+
+    /**
+     * Denotes haptic feedback.
+     */
+    public static final int FEEDBACK_HAPTIC =  0x0000002;
+
+    /**
+     * Denotes audible (not spoken) feedback.
+     */
+    public static final int FEEDBACK_AUDIBLE = 0x0000004;
+
+    /**
+     * Denotes visual feedback.
+     */
+    public static final int FEEDBACK_VISUAL = 0x0000008;
+
+    /**
+     * Denotes generic feedback.
+     */
+    public static final int FEEDBACK_GENERIC = 0x0000010;
+
+    /**
+     * Denotes braille feedback.
+     */
+    public static final int FEEDBACK_BRAILLE = 0x0000020;
+
+    /**
+     * 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 = 0x0000001;
+
+    /**
+     * 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 = 0x0000002;
+
+    /**
+     * 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 = 0x0000004;
+
+    /**
+     * @deprecated No longer used
+     */
+    public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 0x00000008;
+
+    /**
+     * 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 = 0x00000010;
+
+    /**
+     * 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 = 0x00000020;
+
+    /**
+     * 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 = 0x00000040;
+
+    /**
+     * 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 = 0x00000080;
+
+     /**
+     * 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.
+     */
+    public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 0x00000100;
+
+    /**
+     * 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 = 0x00000200;
+
+    /**
+     * 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 = 0x00000400;
+
+    /** {@hide} */
+    public static final int FLAG_FORCE_DIRECT_BOOT_AWARE = 0x00010000;
+
+    /**
+     * 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>
+     * @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
+     */
+    public int flags;
+
+    /**
+     * Whether or not the service has crashed and is awaiting restart. Only valid from {@link
+     * android.view.accessibility.AccessibilityManager#getEnabledAccessibilityServiceList(int)},
+     * 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.
+     */
+    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;
+
+    /**
+     * 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 description of the accessibility service.
+     */
+    private int mDescriptionResId;
+
+    /**
+     * Non localized description of the accessibility service.
+     */
+    private String mNonLocalizedDescription;
+
+    /**
+     * Creates a new instance.
+     */
+    public AccessibilityServiceInfo() {
+        /* do nothing */
+    }
+
+    /**
+     * 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;
+            }
+            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();
+                }
+            }
+            asAttributes.recycle();
+        } catch (NameNotFoundException e) {
+            throw new XmlPullParserException( "Unable to create context for: "
+                    + serviceInfo.packageName);
+        } finally {
+            if (parser != null) {
+                parser.close();
+            }
+        }
+    }
+
+    /**
+     * Updates the properties that an AccessibilitySerivice can change dynamically.
+     *
+     * @param other The info from which to update the properties.
+     *
+     * @hide
+     */
+    public void updateDynamicallyConfigurableProperties(AccessibilityServiceInfo other) {
+        eventTypes = other.eventTypes;
+        packageNames = other.packageNames;
+        feedbackType = other.feedbackType;
+        notificationTimeout = other.notificationTimeout;
+        mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
+        mInteractiveUiTimeout = other.mInteractiveUiTimeout;
+        flags = other.flags;
+    }
+
+    /**
+     * @hide
+     */
+    public void setComponentName(ComponentName component) {
+        mComponentName = component;
+    }
+
+    /**
+     * @hide
+     */
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * The accessibility service id.
+     * <p>
+     *   <strong>Generated by the system.</strong>
+     * </p>
+     * @return The id.
+     */
+    public String getId() {
+        return 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;
+    }
+
+    /**
+     * 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
+     */
+    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
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void setCapabilities(int capabilities) {
+        mCapabilities = capabilities;
+    }
+
+    /**
+     * The localized summary of the accessibility service.
+     * <p>
+     *    <strong>Statically set from
+     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+     * </p>
+     * @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;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * 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;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int describeContents() {
+        return 0;
+    }
+
+    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.writeString(mNonLocalizedDescription);
+    }
+
+    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());
+        mResolveInfo = parcel.readParcelable(null);
+        mSettingsActivityName = parcel.readString();
+        mCapabilities = parcel.readInt();
+        mSummaryResId = parcel.readInt();
+        mNonLocalizedSummary = parcel.readString();
+        mDescriptionResId = parcel.readInt();
+        mNonLocalizedDescription = parcel.readString();
+    }
+
+    @Override
+    public int hashCode() {
+        return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode());
+    }
+
+    @Override
+    public boolean equals(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("summary: ").append(mNonLocalizedSummary);
+        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_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";
+            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_ENHANCED_WEB_ACCESSIBILITY:
+                return "CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY";
+            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";
+            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));
+            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/accessibilityservice/FingerprintGestureController.java b/android/accessibilityservice/FingerprintGestureController.java
new file mode 100644
index 0000000..c30030d
--- /dev/null
+++ b/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/accessibilityservice/GestureDescription.java b/android/accessibilityservice/GestureDescription.java
new file mode 100644
index 0000000..a3e7ad5
--- /dev/null
+++ b/android/accessibilityservice/GestureDescription.java
@@ -0,0 +1,568 @@
+/*
+ * 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 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.
+ * <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 = 10;
+
+    /**
+     * 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];
+
+    /**
+     * 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() {}
+
+    private GestureDescription(List<StrokeDescription> strokes) {
+        mStrokes.addAll(strokes);
+    }
+
+    /**
+     * 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);
+    }
+
+    /**
+     * 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<>();
+
+        /**
+         * Add 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");
+            }
+
+            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;
+        }
+
+        public GestureDescription build() {
+            if (mStrokes.size() == 0) {
+                throw new IllegalStateException("Gestures must have at least one stroke");
+            }
+            return new GestureDescription(mStrokes);
+        }
+    }
+
+    /**
+     * 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());
+            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/accounts/AbstractAccountAuthenticator.java b/android/accounts/AbstractAccountAuthenticator.java
new file mode 100644
index 0000000..25cd342
--- /dev/null
+++ b/android/accounts/AbstractAccountAuthenticator.java
@@ -0,0 +1,997 @@
+/*
+ * 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.Manifest;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.os.Binder;
+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_MANAGER_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.
+ * </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).
+ * The {@link AccountAuthenticatorActivity} handles this, so one may wish to extend that when
+ * writing activities to handle these requests.
+ */
+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";
+
+    private final Context mContext;
+
+    public AbstractAccountAuthenticator(Context context) {
+        mContext = context;
+    }
+
+    private class Transport extends IAccountAuthenticator.Stub {
+        @Override
+        public void addAccount(IAccountAuthenticatorResponse response, String accountType,
+                String authTokenType, String[] features, Bundle options)
+                throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "addAccount: accountType " + accountType
+                        + ", authTokenType " + authTokenType
+                        + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+            }
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void confirmCredentials(IAccountAuthenticatorResponse response,
+                Account account, Bundle options) throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "confirmCredentials: " + account);
+            }
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void getAuthTokenLabel(IAccountAuthenticatorResponse response,
+                String authTokenType)
+                throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "getAuthTokenLabel: authTokenType " + authTokenType);
+            }
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void getAuthToken(IAccountAuthenticatorResponse response,
+                Account account, String authTokenType, Bundle loginOptions)
+                throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "getAuthToken: " + account
+                        + ", authTokenType " + authTokenType);
+            }
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void updateCredentials(IAccountAuthenticatorResponse response, Account account,
+                String authTokenType, Bundle loginOptions) throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "updateCredentials: " + account
+                        + ", authTokenType " + authTokenType);
+            }
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void editProperties(IAccountAuthenticatorResponse response,
+                String accountType) throws RemoteException {
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void hasFeatures(IAccountAuthenticatorResponse response,
+                Account account, String[] features) throws RemoteException {
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void getAccountRemovalAllowed(IAccountAuthenticatorResponse response,
+                Account account) throws RemoteException {
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void getAccountCredentialsForCloning(IAccountAuthenticatorResponse response,
+                Account account) throws RemoteException {
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void addAccountFromCredentials(IAccountAuthenticatorResponse response,
+                Account account,
+                Bundle accountCredentials) throws RemoteException {
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void startAddAccountSession(IAccountAuthenticatorResponse response,
+                String accountType, String authTokenType, String[] features, Bundle options)
+                throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG,
+                        "startAddAccountSession: accountType " + accountType
+                        + ", authTokenType " + authTokenType
+                        + ", features " + (features == null ? "[]" : Arrays.toString(features)));
+            }
+            checkBinderPermission();
+            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);
+            }
+        }
+
+        @Override
+        public void startUpdateCredentialsSession(
+                IAccountAuthenticatorResponse response,
+                Account account,
+                String authTokenType,
+                Bundle loginOptions) throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "startUpdateCredentialsSession: "
+                        + account
+                        + ", authTokenType "
+                        + authTokenType);
+            }
+            checkBinderPermission();
+            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);
+
+            }
+        }
+
+        @Override
+        public void finishSession(
+                IAccountAuthenticatorResponse response,
+                String accountType,
+                Bundle sessionBundle) throws RemoteException {
+            if (Log.isLoggable(TAG, Log.VERBOSE)) {
+                Log.v(TAG, "finishSession: accountType " + accountType);
+            }
+            checkBinderPermission();
+            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);
+
+            }
+        }
+
+        @Override
+        public void isCredentialsUpdateSuggested(
+                IAccountAuthenticatorResponse response,
+                Account account,
+                String statusToken) throws RemoteException {
+            checkBinderPermission();
+            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 void checkBinderPermission() {
+        final int uid = Binder.getCallingUid();
+        final String perm = Manifest.permission.ACCOUNT_MANAGER;
+        if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
+            throw new SecurityException("caller uid " + uid + " lacks " + perm);
+        }
+    }
+
+    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);
+        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/accounts/Account.java b/android/accounts/Account.java
new file mode 100644
index 0000000..c822d20
--- /dev/null
+++ b/android/accounts/Account.java
@@ -0,0 +1,179 @@
+/*
+ * 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.annotation.UnsupportedAppUsage;
+import android.content.Context;
+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}
+ */
+public class Account implements Parcelable {
+    @UnsupportedAppUsage
+    private static final String TAG = "Account";
+
+    @GuardedBy("sAccessedAccounts")
+    private static final Set<Account> sAccessedAccounts = new ArraySet<>();
+
+    public final String name;
+    public final String type;
+    private String mSafeName;
+    @UnsupportedAppUsage
+    private final @Nullable String accessId;
+
+    public boolean equals(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(String name, String type) {
+        this(name, type, null);
+    }
+
+    /**
+     * @hide
+     */
+    public Account(@NonNull Account other, @NonNull String accessId) {
+        this(other.name, other.type, accessId);
+    }
+
+    /**
+     * @hide
+     */
+    public Account(String name, 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)) {
+                    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);
+                    }
+                }
+            }
+        }
+    }
+
+    /** @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/accounts/AccountAndUser.java b/android/accounts/AccountAndUser.java
new file mode 100644
index 0000000..b0d5343
--- /dev/null
+++ b/android/accounts/AccountAndUser.java
@@ -0,0 +1,54 @@
+/*
+ * 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.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(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/accounts/AccountAuthenticatorActivity.java b/android/accounts/AccountAuthenticatorActivity.java
new file mode 100644
index 0000000..967aa04
--- /dev/null
+++ b/android/accounts/AccountAuthenticatorActivity.java
@@ -0,0 +1,82 @@
+/*
+ * 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.
+ */
+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);
+
+        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/accounts/AccountAuthenticatorResponse.java b/android/accounts/AccountAuthenticatorResponse.java
new file mode 100644
index 0000000..bb2e327
--- /dev/null
+++ b/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.annotation.UnsupportedAppUsage;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.Parcel;
+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/accounts/AccountManager.java b/android/accounts/AccountManager.java
new file mode 100644
index 0000000..26c2c0c
--- /dev/null
+++ b/android/accounts/AccountManager.java
@@ -0,0 +1,3346 @@
+/*
+ * 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.BroadcastBehavior;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+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.UnsupportedAppUsage;
+import android.app.Activity;
+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.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.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.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";
+
+    @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
+    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) {
+        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();
+        }
+    }
+
+    /**
+     * 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.
+     *
+     * @return An array of {@link AuthenticatorDescription} for every
+     *     authenticator known to the AccountManager service.  Empty (never
+     *     null) if no authenticators are known.
+     */
+    public AuthenticatorDescription[] getAuthenticatorTypes() {
+        try {
+            return mService.getAuthenticatorTypes(UserHandle.getCallingUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @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 AbstractAcccountAuthenticator managing the account did so or because the
+     * client shares a signature with the managing AbstractAccountAuthenticator.
+     *
+     * <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[] getAccounts() {
+        try {
+            return mService.getAccounts(null, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @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) {
+        try {
+            return mService.getAccountsAsUser(null, userId, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @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 AbstractAcccountAuthenticator 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>
+     * 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 AbstractAcccountAuthenticator
+     * 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.
+     */
+    @NonNull
+    public Account[] getAccountsByType(String type) {
+        return getAccountsByTypeAsUser(type, mContext.getUser());
+    }
+
+    /** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */
+    @NonNull
+    @UnsupportedAppUsage
+    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.
+     */
+    public AccountManagerFuture<Boolean> hasFeatures(final Account account,
+            final String[] features,
+            AccountManagerCallback<Boolean> callback, Handler handler) {
+        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, 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 AbstractAcccountAuthenticator
+     * 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);
+        } 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.
+     *
+     * @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);
+        } 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
+     * @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.
+     */
+    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
+     */
+    @Deprecated
+    public AccountManagerFuture<Boolean> removeAccount(final Account account,
+            AccountManagerCallback<Boolean> callback, Handler handler) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        return new Future2Task<Boolean>(handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.removeAccount(mResponse, account, false);
+            }
+            @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();
+    }
+
+    /**
+     * 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>
+     */
+    public AccountManagerFuture<Bundle> removeAccount(final Account account,
+            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        return new AmsTask(activity, handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.removeAccount(mResponse, account, activity != null);
+            }
+        }.start();
+    }
+
+    /**
+     * @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>
+     */
+    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 (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();
+    }
+
+    /**
+     * @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 wether 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();
+    }
+
+    /**
+     * @hide
+     * Removes the shared account.
+     * @param account the account to remove
+     * @param user the user to remove the account from
+     * @return
+     */
+    public boolean removeSharedAccount(final Account account, UserHandle user) {
+        try {
+            boolean val = mService.removeSharedAccountAsUser(account, user.getIdentifier());
+            return val;
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * @param user
+     * @return
+     */
+    public Account[] getSharedAccounts(UserHandle user) {
+        try {
+            return mService.getSharedAccountsAsUser(user.getIdentifier());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * 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>
+     */
+    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
+    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 e) {
+                throw new OperationCanceledException();
+            } catch (TimeoutException e) {
+                // fall through and cancel
+            } catch (InterruptedException 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 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);
+                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 AbstractAcccountAuthenticator 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 AbstractAcccountAuthenticator 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 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>
+     * @see #startAddAccountSession and #startUpdateCredentialsSession
+     */
+    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(android.Manifest.permission.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();
+        }
+    }
+}
diff --git a/android/accounts/AccountManagerCallback.java b/android/accounts/AccountManagerCallback.java
new file mode 100644
index 0000000..4aa7169
--- /dev/null
+++ b/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/accounts/AccountManagerFuture.java b/android/accounts/AccountManagerFuture.java
new file mode 100644
index 0000000..77670d9
--- /dev/null
+++ b/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/accounts/AccountManagerInternal.java b/android/accounts/AccountManagerInternal.java
new file mode 100644
index 0000000..68c17c3
--- /dev/null
+++ b/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/accounts/AccountManagerPerfTest.java b/android/accounts/AccountManagerPerfTest.java
new file mode 100644
index 0000000..e455e6a
--- /dev/null
+++ b/android/accounts/AccountManagerPerfTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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 static junit.framework.Assert.fail;
+
+import android.Manifest;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.perftests.utils.BenchmarkState;
+import android.perftests.utils.PerfStatusReporter;
+
+import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.LargeTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class AccountManagerPerfTest {
+
+    @Rule
+    public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
+
+    @Test
+    public void testGetAccounts() {
+        BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Context context = InstrumentationRegistry.getTargetContext();
+        if (context.checkSelfPermission(Manifest.permission.GET_ACCOUNTS)
+                != PackageManager.PERMISSION_GRANTED) {
+            fail("Missing required GET_ACCOUNTS permission");
+        }
+        AccountManager accountManager = AccountManager.get(context);
+        while (state.keepRunning()) {
+            accountManager.getAccounts();
+        }
+    }
+}
diff --git a/android/accounts/AccountManagerResponse.java b/android/accounts/AccountManagerResponse.java
new file mode 100644
index 0000000..369a7c3
--- /dev/null
+++ b/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/accounts/AccountsException.java b/android/accounts/AccountsException.java
new file mode 100644
index 0000000..b997390
--- /dev/null
+++ b/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/accounts/AuthenticatorDescription.java b/android/accounts/AuthenticatorDescription.java
new file mode 100644
index 0000000..5556394
--- /dev/null
+++ b/android/accounts/AuthenticatorDescription.java
@@ -0,0 +1,149 @@
+/*
+ * 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.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcelable;
+import android.os.Parcel;
+
+/**
+ * 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(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/accounts/AuthenticatorException.java b/android/accounts/AuthenticatorException.java
new file mode 100644
index 0000000..f778d7d
--- /dev/null
+++ b/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/accounts/CantAddAccountActivity.java b/android/accounts/CantAddAccountActivity.java
new file mode 100644
index 0000000..f7f232e
--- /dev/null
+++ b/android/accounts/CantAddAccountActivity.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.app.Activity;
+import android.os.Bundle;
+import android.view.View;
+
+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);
+    }
+
+    public void onCancelButtonClicked(View view) {
+        onBackPressed();
+    }
+}
diff --git a/android/accounts/ChooseAccountActivity.java b/android/accounts/ChooseAccountActivity.java
new file mode 100644
index 0000000..4af22bf
--- /dev/null
+++ b/android/accounts/ChooseAccountActivity.java
@@ -0,0 +1,223 @@
+/*
+ * 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.app.ActivityManager;
+import android.app.ActivityTaskManager;
+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.IBinder;
+import android.os.Parcelable;
+import android.os.RemoteException;
+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);
+        mAccounts = getIntent().getParcelableArrayExtra(AccountManager.KEY_ACCOUNTS);
+        mAccountManagerResponse =
+                getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_MANAGER_RESPONSE);
+
+        // KEY_ACCOUNTS is a required parameter
+        if (mAccounts == null) {
+            setResult(RESULT_CANCELED);
+            finish();
+            return;
+        }
+
+        try {
+            IBinder activityToken = getActivityToken();
+            mCallingUid = ActivityTaskManager.getService().getLaunchedFromUid(activityToken);
+            mCallingPackage = ActivityTaskManager.getService().getLaunchedFromPackage(
+                    activityToken);
+        } catch (RemoteException re) {
+            // Couldn't figure out caller details
+            Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re);
+        }
+
+        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);
+        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/accounts/ChooseAccountTypeActivity.java b/android/accounts/ChooseAccountTypeActivity.java
new file mode 100644
index 0000000..e3352bc
--- /dev/null
+++ b/android/accounts/ChooseAccountTypeActivity.java
@@ -0,0 +1,203 @@
+/*
+ * 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);
+
+        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();
+                }
+                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/accounts/ChooseTypeAndAccountActivity.java b/android/accounts/ChooseTypeAndAccountActivity.java
new file mode 100644
index 0000000..57c1083
--- /dev/null
+++ b/android/accounts/ChooseTypeAndAccountActivity.java
@@ -0,0 +1,629 @@
+/*
+ * 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.ActivityTaskManager;
+import com.google.android.collect.Sets;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.Parcelable;
+import android.os.RemoteException;
+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 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 + ")");
+        }
+
+        String message = null;
+
+        try {
+            IBinder activityToken = getActivityToken();
+            mCallingUid = ActivityTaskManager.getService().getLaunchedFromUid(activityToken);
+            mCallingPackage = ActivityTaskManager.getService().getLaunchedFromPackage(
+                    activityToken);
+            if (mCallingUid != 0 && mCallingPackage != null) {
+                Bundle restrictions = UserManager.get(this)
+                        .getUserRestrictions(new UserHandle(UserHandle.getUserId(mCallingUid)));
+                mDisallowAddAccounts =
+                        restrictions.getBoolean(UserManager.DISALLOW_MODIFY_ACCOUNTS, false);
+            }
+        } catch (RemoteException re) {
+            // Couldn't figure out caller details
+            Log.w(getClass().getSimpleName(), "Unable to get caller identity \n" + re);
+        }
+
+        // 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);
+            if (selectedAccount != null) {
+                mSelectedAccountName = selectedAccount.name;
+            }
+            mAccounts = getAcceptableAccountChoices(AccountManager.get(this));
+        }
+
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "selected account name is " + mSelectedAccountName);
+        }
+
+        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);
+            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 + ", extras=" + extras + ")");
+        }
+
+        // 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);
+            if (intent != null) {
+                mPendingRequest = REQUEST_ADD_ACCOUNT;
+                mExistingAccounts = AccountManager.get(this).getAccountsForPackage(mCallingPackage,
+                        mCallingUid);
+                intent.setFlags(intent.getFlags() & ~Intent.FLAG_ACTIVITY_NEW_TASK);
+                startActivityForResult(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);
+      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 "
+                    + accountName + ", " + accountType);
+        }
+
+        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 whitelisted 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/accounts/GrantCredentialsPermissionActivity.java b/android/accounts/GrantCredentialsPermissionActivity.java
new file mode 100644
index 0000000..af74b03
--- /dev/null
+++ b/android/accounts/GrantCredentialsPermissionActivity.java
@@ -0,0 +1,198 @@
+/*
+ * 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.res.Resources;
+import android.os.Bundle;
+import android.widget.TextView;
+import android.widget.LinearLayout;
+import android.view.View;
+import android.view.LayoutInflater;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.text.TextUtils;
+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 Bundle mResultBundle = null;
+    protected LayoutInflater mInflater;
+
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        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);
+        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;
+        }
+
+        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);
+        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/accounts/NetworkErrorException.java b/android/accounts/NetworkErrorException.java
new file mode 100644
index 0000000..07f4ce9
--- /dev/null
+++ b/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/accounts/OnAccountsUpdateListener.java b/android/accounts/OnAccountsUpdateListener.java
new file mode 100644
index 0000000..2b4ee50
--- /dev/null
+++ b/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/accounts/OperationCanceledException.java b/android/accounts/OperationCanceledException.java
new file mode 100644
index 0000000..896d194
--- /dev/null
+++ b/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/animation/AnimationHandler.java b/android/animation/AnimationHandler.java
new file mode 100644
index 0000000..260323f
--- /dev/null
+++ b/android/animation/AnimationHandler.java
@@ -0,0 +1,317 @@
+/*
+ * 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.os.SystemClock;
+import android.util.ArrayMap;
+import android.view.Choreographer;
+
+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 {
+    /**
+     * Internal per-thread collections used to avoid set collisions as animations start and end
+     * while being processed.
+     * @hide
+     */
+    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;
+
+    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 boolean mListDirty = false;
+
+    public static AnimationHandler getInstance() {
+        if (sAnimatorHandler.get() == null) {
+            sAnimatorHandler.set(new AnimationHandler());
+        }
+        return sAnimatorHandler.get();
+    }
+
+    /**
+     * 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 = 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.
+     */
+    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/animation/AnimationThread.java b/android/animation/AnimationThread.java
new file mode 100644
index 0000000..ce2aec7
--- /dev/null
+++ b/android/animation/AnimationThread.java
@@ -0,0 +1,176 @@
+/*
+ * 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 com.android.ide.common.rendering.api.IAnimationListener;
+import com.android.ide.common.rendering.api.RenderSession;
+import com.android.ide.common.rendering.api.Result;
+import com.android.ide.common.rendering.api.Result.Status;
+import com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.RenderSessionImpl;
+
+import android.os.Handler;
+import android.os.Handler_Delegate;
+import android.os.Message;
+
+import java.util.PriorityQueue;
+import java.util.Queue;
+
+/**
+ * Abstract animation thread.
+ * <p/>
+ * This does not actually start an animation, instead it fakes a looper that will play whatever
+ * animation is sending messages to its own {@link Handler}.
+ * <p/>
+ * Classes should implement {@link #preAnimation()} and {@link #postAnimation()}.
+ * <p/>
+ * If {@link #preAnimation()} does not start an animation somehow then the thread doesn't do
+ * anything.
+ *
+ */
+public abstract class AnimationThread extends Thread {
+
+    private static class MessageBundle implements Comparable<MessageBundle> {
+        final Handler mTarget;
+        final Message mMessage;
+        final long mUptimeMillis;
+
+        MessageBundle(Handler target, Message message, long uptimeMillis) {
+            mTarget = target;
+            mMessage = message;
+            mUptimeMillis = uptimeMillis;
+        }
+
+        @Override
+        public int compareTo(MessageBundle bundle) {
+            if (mUptimeMillis < bundle.mUptimeMillis) {
+                return -1;
+            }
+            return 1;
+        }
+    }
+
+    private final RenderSessionImpl mSession;
+
+    private Queue<MessageBundle> mQueue = new PriorityQueue<MessageBundle>();
+    private final IAnimationListener mListener;
+
+    public AnimationThread(RenderSessionImpl scene, String threadName,
+            IAnimationListener listener) {
+        super(threadName);
+        mSession = scene;
+        mListener = listener;
+    }
+
+    public abstract Result preAnimation();
+    public abstract void postAnimation();
+
+    @Override
+    public void run() {
+        Bridge.prepareThread();
+        try {
+            /* FIXME: The ANIMATION_FRAME message no longer exists.  Instead, the
+             * animation timing loop is completely based on a Choreographer objects
+             * that schedules animation and drawing frames.  The animation handler is
+             * no longer even a handler; it is just a Runnable enqueued on the Choreographer.
+            Handler_Delegate.setCallback(new IHandlerCallback() {
+                @Override
+                public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) {
+                    if (msg.what == ValueAnimator.ANIMATION_START ||
+                            msg.what == ValueAnimator.ANIMATION_FRAME) {
+                        mQueue.add(new MessageBundle(handler, msg, uptimeMillis));
+                    } else {
+                        // just ignore.
+                    }
+                }
+            });
+            */
+
+            // call out to the pre-animation work, which should start an animation or more.
+            Result result = preAnimation();
+            if (result.isSuccess() == false) {
+                mListener.done(result);
+            }
+
+            // loop the animation
+            RenderSession session = mSession.getSession();
+            do {
+                // check early.
+                if (mListener.isCanceled()) {
+                    break;
+                }
+
+                // get the next message.
+                MessageBundle bundle = mQueue.poll();
+                if (bundle == null) {
+                    break;
+                }
+
+                // sleep enough for this bundle to be on time
+                long currentTime = System.currentTimeMillis();
+                if (currentTime < bundle.mUptimeMillis) {
+                    try {
+                        sleep(bundle.mUptimeMillis - currentTime);
+                    } catch (InterruptedException e) {
+                        // FIXME log/do something/sleep again?
+                        e.printStackTrace();
+                    }
+                }
+
+                // check after sleeping.
+                if (mListener.isCanceled()) {
+                    break;
+                }
+
+                // ready to do the work, acquire the scene.
+                result = mSession.acquire(250);
+                if (result.isSuccess() == false) {
+                    mListener.done(result);
+                    return;
+                }
+
+                // process the bundle. If the animation is not finished, this will enqueue
+                // the next message, so mQueue will have another one.
+                try {
+                    // check after acquiring in case it took a while.
+                    if (mListener.isCanceled()) {
+                        break;
+                    }
+
+                    bundle.mTarget.handleMessage(bundle.mMessage);
+                    if (mSession.render(false /*freshRender*/).isSuccess()) {
+                        mListener.onNewFrame(session);
+                    }
+                } finally {
+                    mSession.release();
+                }
+            } while (mListener.isCanceled() == false && mQueue.size() > 0);
+
+            mListener.done(Status.SUCCESS.createResult());
+
+        } catch (Throwable throwable) {
+            // can't use Bridge.getLog() as the exception might be thrown outside
+            // of an acquire/release block.
+            mListener.done(Status.ERROR_UNKNOWN.createResult("Error playing animation", throwable));
+
+        } finally {
+            postAnimation();
+            Handler_Delegate.setCallback(null);
+            Bridge.cleanupThread();
+        }
+    }
+}
diff --git a/android/animation/Animator.java b/android/animation/Animator.java
new file mode 100644
index 0000000..17d54d2
--- /dev/null
+++ b/android/animation/Animator.java
@@ -0,0 +1,678 @@
+/*
+ * 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.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.content.pm.ActivityInfo.Config;
+import android.content.res.ConstantState;
+
+import java.util.ArrayList;
+
+/**
+ * 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;
+
+    /**
+     * 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() {
+        if (isStarted() && !mPaused) {
+            mPaused = true;
+            if (mPauseListeners != null) {
+                ArrayList<AnimatorPauseListener> tmpListeners =
+                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onAnimationPause(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * 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;
+            if (mPauseListeners != null) {
+                ArrayList<AnimatorPauseListener> tmpListeners =
+                        (ArrayList<AnimatorPauseListener>) mPauseListeners.clone();
+                int numListeners = tmpListeners.size();
+                for (int i = 0; i < numListeners; ++i) {
+                    tmpListeners.get(i).onAnimationResume(this);
+                }
+            }
+        }
+    }
+
+    /**
+     * 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);
+            }
+            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
+    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.
+     */
+    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {}
+
+    /**
+     * <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(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(Animator animation, boolean isReverse) {
+            onAnimationEnd(animation);
+        }
+
+        /**
+         * <p>Notifies the start of the animation.</p>
+         *
+         * @param animation The started animation.
+         */
+        void onAnimationStart(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(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(Animator animation);
+
+        /**
+         * <p>Notifies the repetition of the animation.</p>
+         *
+         * @param animation The animation which was repeated.
+         */
+        void onAnimationRepeat(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(Animator animation);
+
+        /**
+         * <p>Notifies that the animation was resumed, after being
+         * previously paused.</p>
+         *
+         * @param animation The animation being resumed.
+         * @see #resume()
+         */
+        void onAnimationResume(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;
+        }
+    }
+}
diff --git a/android/animation/AnimatorInflater.java b/android/animation/AnimatorInflater.java
new file mode 100644
index 0000000..f69bbfd
--- /dev/null
+++ b/android/animation/AnimatorInflater.java
@@ -0,0 +1,1082 @@
+/*
+ * 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));
+        }
+        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);
+                    // 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;
+        }
+        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);
+                    // 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/animation/AnimatorListenerAdapter.java b/android/animation/AnimatorListenerAdapter.java
new file mode 100644
index 0000000..2ecb8c3
--- /dev/null
+++ b/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/animation/AnimatorSet.java b/android/animation/AnimatorSet.java
new file mode 100644
index 0000000..35cf39f
--- /dev/null
+++ b/android/animation/AnimatorSet.java
@@ -0,0 +1,2099 @@
+/*
+ * 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.view.animation.Animation;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * 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;
+
+    // This is to work around a bug in b/34736819. This needs to be removed once app team
+    // fixes their side.
+    private AnimatorListenerAdapter mDummyListener = 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()) {
+            ArrayList<AnimatorListener> tmpListeners = null;
+            if (mListeners != null) {
+                tmpListeners = (ArrayList<AnimatorListener>) mListeners.clone();
+                int size = tmpListeners.size();
+                for (int i = 0; i < size; i++) {
+                    tmpListeners.get(i).onAnimationCancel(this);
+                }
+            }
+            ArrayList<Node> playingSet = new ArrayList<>(mPlayingSet);
+            int setSize = playingSet.size();
+            for (int i = 0; i < setSize; i++) {
+                playingSet.get(i).mAnimation.cancel();
+            }
+            mPlayingSet.clear();
+            endAnimation();
+        }
+    }
+
+    // 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()) {
+            // 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;
+                while (mLastEventId > 0) {
+                    mLastEventId = mLastEventId - 1;
+                    AnimationEvent event = mEvents.get(mLastEventId);
+                    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 {
+                while (mLastEventId < mEvents.size() - 1) {
+                    // Avoid potential reentrant loop caused by child animators manipulating
+                    // AnimatorSet's lifecycle (i.e. not a recommended approach).
+                    mLastEventId = mLastEventId + 1;
+                    AnimationEvent event = mEvents.get(mLastEventId);
+                    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();
+                    }
+                }
+            }
+            mPlayingSet.clear();
+        }
+        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;
+        }
+    }
+
+    @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);
+            }
+        }
+    }
+
+    /**
+     * {@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");
+        }
+        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();
+        }
+
+        if (mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationStart(this, 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) {
+        if (!isInitialized()) {
+            throw new UnsupportedOperationException("Children must be initialized.");
+        }
+
+        // This makes sure the animation events are sorted an up to date.
+        initAnimation();
+
+        // 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--) {
+                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                    mEvents.get(i).mNode.mAnimation.skipToEndValue(true);
+                }
+            }
+        } else {
+            for (int i = 0; i < mEvents.size(); i++) {
+                if (mEvents.get(i).mEvent == AnimationEvent.ANIMATION_END) {
+                    mEvents.get(i).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.
+     */
+    @Override
+    void animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
+        if (currentPlayTime < 0 || lastPlayTime < 0) {
+            throw new UnsupportedOperationException("Error: Play time should never be negative.");
+        }
+        // TODO: take into account repeat counts and repeat callback when repeat is implemented.
+        // Clamp currentPlayTime and lastPlayTime
+
+        // TODO: Make this more efficient
+
+        // Convert the play times to the forward direction.
+        if (inReverse) {
+            if (getTotalDuration() == DURATION_INFINITE) {
+                throw new UnsupportedOperationException("Cannot reverse AnimatorSet with infinite"
+                        + " duration");
+            }
+            long duration = getTotalDuration() - mStartDelay;
+            currentPlayTime = Math.min(currentPlayTime, duration);
+            currentPlayTime = duration - currentPlayTime;
+            lastPlayTime = duration - lastPlayTime;
+            inReverse = false;
+        }
+
+        ArrayList<Node> unfinishedNodes = new ArrayList<>();
+        // Assumes forward playing from here on.
+        for (int i = 0; i < mEvents.size(); i++) {
+            AnimationEvent event = mEvents.get(i);
+            if (event.getTime() > currentPlayTime || event.getTime() == DURATION_INFINITE) {
+                break;
+            }
+
+            // This animation started prior to the current play time, and won't finish before the
+            // play time, add to the unfinished list.
+            if (event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                if (event.mNode.mEndTime == DURATION_INFINITE
+                        || event.mNode.mEndTime > currentPlayTime) {
+                    unfinishedNodes.add(event.mNode);
+                }
+            }
+            // For animations that do finish before the play time, end them in the sequence that
+            // they would in a normal run.
+            if (event.mEvent == AnimationEvent.ANIMATION_END) {
+                // Skip to the end of the animation.
+                event.mNode.mAnimation.skipToEndValue(false);
+            }
+        }
+
+        // Seek unfinished animation to the right time.
+        for (int i = 0; i < unfinishedNodes.size(); i++) {
+            Node node = unfinishedNodes.get(i);
+            long playTime = getPlayTimeForNode(currentPlayTime, node, inReverse);
+            if (!inReverse) {
+                playTime -= node.mAnimation.getStartDelay();
+            }
+            node.mAnimation.animateBasedOnPlayTime(playTime, lastPlayTime, inReverse);
+        }
+
+        // Seek not yet started animations.
+        for (int i = 0; i < mEvents.size(); i++) {
+            AnimationEvent event = mEvents.get(i);
+            if (event.getTime() > currentPlayTime
+                    && event.mEvent == AnimationEvent.ANIMATION_DELAY_ENDED) {
+                event.mNode.mAnimation.skipToEndValue(true);
+            }
+        }
+
+    }
+
+    @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;
+    }
+
+    private void skipToStartValue(boolean inReverse) {
+        skipToEndValue(!inReverse);
+    }
+
+    /**
+     * 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.
+     *                 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();
+
+        if (!isStarted() || isPaused()) {
+            if (mReversing) {
+                throw new UnsupportedOperationException("Error: Something went wrong. mReversing"
+                        + " should not be set when AnimatorSet is not started.");
+            }
+            if (!mSeekState.isActive()) {
+                findLatestEventIdForTime(0);
+                // Set all the values to start values.
+                initChildren();
+                mSeekState.setPlayTime(0, mReversing);
+            }
+            animateBasedOnPlayTime(playTime, 0, mReversing);
+            mSeekState.setPlayTime(playTime, mReversing);
+        } else {
+            // If the animation is running, just set the seek time and wait until the next frame
+            // (i.e. doAnimationFrame(...)) to advance the animation.
+            mSeekState.setPlayTime(playTime, 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;
+            // Forcefully initialize all children based on their end time, so that if the start
+            // value of a child is dependent on a previous animation, the animation will be
+            // initialized after the the previous animations have been advanced to the end.
+            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, getPlayTimeForNode(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, getPlayTimeForNode(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, getPlayTimeForNode(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;
+            node.mEnded = node.mAnimation.pulseAnimationFrame(
+                    (long) (animPlayTime * durationScale));
+        }
+    }
+
+    private long getPlayTimeForNode(long overallPlayTime, Node node) {
+        return getPlayTimeForNode(overallPlayTime, node, mReversing);
+    }
+
+    private long getPlayTimeForNode(long overallPlayTime, Node node, boolean inReverse) {
+        if (inReverse) {
+            overallPlayTime = getTotalDuration() - overallPlayTime;
+            return node.mEndTime - overallPlayTime;
+        } else {
+            return overallPlayTime - node.mStartTime;
+        }
+    }
+
+    private void startAnimation() {
+        addDummyListener();
+
+        // 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 addDummyListener() {
+        for (int i = 1; i < mNodes.size(); i++) {
+            mNodes.get(i).mAnimation.addListener(mDummyListener);
+        }
+    }
+
+    private void removeDummyListener() {
+        for (int i = 1; i < mNodes.size(); i++) {
+            mNodes.get(i).mAnimation.removeListener(mDummyListener);
+        }
+    }
+
+    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();
+        // Call end listener
+        if (mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationEnd(this, mReversing);
+            }
+        }
+        removeDummyListener();
+        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.mPlayingSet = new ArrayList<Node>();
+        anim.mNodeMap = new ArrayMap<Animator, Node>();
+        anim.mNodes = new ArrayList<Node>(nodeCount);
+        anim.mEvents = new ArrayList<AnimationEvent>();
+        anim.mDummyListener = 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(mDummyListener);
+            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 (int) (t1 - t2);
+            }
+        });
+
+        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) {
+            // TODO: This can be simplified.
+
+            // Clamp the play time
+            if (getTotalDuration() != DURATION_INFINITE) {
+                mPlayTime = Math.min(playTime, getTotalDuration() - mStartDelay);
+            }
+            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 dummy ValueAnimator just to run the clock
+            ValueAnimator anim = ValueAnimator.ofFloat(0f, 1f);
+            anim.setDuration(delay);
+            after(anim);
+            return this;
+        }
+
+    }
+
+}
diff --git a/android/animation/ArgbEvaluator.java b/android/animation/ArgbEvaluator.java
new file mode 100644
index 0000000..5b69d18
--- /dev/null
+++ b/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.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/animation/BidirectionalTypeConverter.java b/android/animation/BidirectionalTypeConverter.java
new file mode 100644
index 0000000..960650e
--- /dev/null
+++ b/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/animation/FloatArrayEvaluator.java b/android/animation/FloatArrayEvaluator.java
new file mode 100644
index 0000000..9ae1197
--- /dev/null
+++ b/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/animation/FloatEvaluator.java b/android/animation/FloatEvaluator.java
new file mode 100644
index 0000000..9463aa1
--- /dev/null
+++ b/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 * (v1 - v0)</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/animation/FloatKeyframeSet.java b/android/animation/FloatKeyframeSet.java
new file mode 100644
index 0000000..11837b5
--- /dev/null
+++ b/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/animation/IntArrayEvaluator.java b/android/animation/IntArrayEvaluator.java
new file mode 100644
index 0000000..d7f10f3
--- /dev/null
+++ b/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/animation/IntEvaluator.java b/android/animation/IntEvaluator.java
new file mode 100644
index 0000000..1de2ae7
--- /dev/null
+++ b/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/animation/IntKeyframeSet.java b/android/animation/IntKeyframeSet.java
new file mode 100644
index 0000000..f1e146e
--- /dev/null
+++ b/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/animation/Keyframe.java b/android/animation/Keyframe.java
new file mode 100644
index 0000000..bcb94d1
--- /dev/null
+++ b/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/animation/KeyframeSet.java b/android/animation/KeyframeSet.java
new file mode 100644
index 0000000..116d063
--- /dev/null
+++ b/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/animation/Keyframes.java b/android/animation/Keyframes.java
new file mode 100644
index 0000000..6666294
--- /dev/null
+++ b/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/animation/LayoutTransition.java b/android/animation/LayoutTransition.java
new file mode 100644
index 0000000..c753710
--- /dev/null
+++ b/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.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/animation/ObjectAnimator.java b/android/animation/ObjectAnimator.java
new file mode 100644
index 0000000..1e1f155
--- /dev/null
+++ b/android/animation/ObjectAnimator.java
@@ -0,0 +1,1017 @@
+/*
+ * 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;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * 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;
+
+    /**
+     * A weak reference to the target object on which the property exists, set
+     * in the constructor. We'll cancel the animation if this goes away.
+     */
+    private WeakReference<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 == null ? null : mTarget.get();
+    }
+
+    @Override
+    public void setTarget(@Nullable Object target) {
+        final Object oldTarget = getTarget();
+        if (oldTarget != target) {
+            if (isStarted()) {
+                cancel();
+            }
+            mTarget = target == null ? null : new WeakReference<Object>(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();
+        if (mTarget != null && target == null) {
+            // We lost the target reference, cancel and clean up. Note: we allow null target if the
+            /// target has never been set.
+            cancel();
+            return;
+        }
+
+        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/animation/PathKeyframes.java b/android/animation/PathKeyframes.java
new file mode 100644
index 0000000..b362904
--- /dev/null
+++ b/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/animation/PointFEvaluator.java b/android/animation/PointFEvaluator.java
new file mode 100644
index 0000000..91d501f
--- /dev/null
+++ b/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/animation/PropertyValuesHolder.java b/android/animation/PropertyValuesHolder.java
new file mode 100644
index 0000000..76806a2
--- /dev/null
+++ b/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/animation/PropertyValuesHolder_Accessor.java b/android/animation/PropertyValuesHolder_Accessor.java
new file mode 100644
index 0000000..576d0ea
--- /dev/null
+++ b/android/animation/PropertyValuesHolder_Accessor.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.PropertyValuesHolder.FloatPropertyValuesHolder;
+import android.animation.PropertyValuesHolder.IntPropertyValuesHolder;
+import android.animation.PropertyValuesHolder.MultiFloatValuesHolder;
+import android.animation.PropertyValuesHolder.MultiIntValuesHolder;
+
+public class PropertyValuesHolder_Accessor {
+    /**
+     * Method used by layoutlib to ensure that {@link Class} instances are not cached by this
+     * class. Caching the {@link Class} instances will lead to leaking the {@link ClassLoader}
+     * used by that class.
+     * In layoutlib, these class loaders are instantiated dynamically to allow for new classes to
+     * be loaded when the user code changes.
+     */
+    public static void clearClassCaches() {
+        PropertyValuesHolder.sGetterPropertyMap.clear();
+        PropertyValuesHolder.sSetterPropertyMap.clear();
+        IntPropertyValuesHolder.sJNISetterPropertyMap.clear();
+        MultiIntValuesHolder.sJNISetterPropertyMap.clear();
+        FloatPropertyValuesHolder.sJNISetterPropertyMap.clear();
+        MultiFloatValuesHolder.sJNISetterPropertyMap.clear();
+    }
+}
diff --git a/android/animation/PropertyValuesHolder_Delegate.java b/android/animation/PropertyValuesHolder_Delegate.java
new file mode 100644
index 0000000..1d7026c
--- /dev/null
+++ b/android/animation/PropertyValuesHolder_Delegate.java
@@ -0,0 +1,195 @@
+/*
+ * 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 com.android.layoutlib.bridge.Bridge;
+import com.android.layoutlib.bridge.impl.DelegateManager;
+import com.android.tools.layoutlib.annotations.LayoutlibDelegate;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Delegate implementing the native methods of android.animation.PropertyValuesHolder
+ *
+ * Through the layoutlib_create tool, the original native methods of PropertyValuesHolder have been
+ * replaced by calls to methods of the same name in this delegate class.
+ *
+ * Because it's a stateless class to start with, there's no need to keep a {@link DelegateManager}
+ * around to map int to instance of the delegate.
+ *
+ * The main goal of this class' methods are to provide a native way to access setters and getters
+ * on some object. We override these methods to use reflection since the original reflection
+ * implementation of the PropertyValuesHolder won't be able to access protected methods.
+ *
+ */
+/*package*/
+@SuppressWarnings("unused")
+class PropertyValuesHolder_Delegate {
+    // This code is copied from android.animation.PropertyValuesHolder and must be kept in sync
+    // 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 final Object sMethodIndexLock = new Object();
+    private static final Map<Long, Method> ID_TO_METHOD = new HashMap<Long, Method>();
+    private static final Map<String, Long> METHOD_NAME_TO_ID = new HashMap<String, Long>();
+    private static long sNextId = 1;
+
+    private static long registerMethod(Class<?> targetClass, String methodName, Class[] types,
+            int nArgs) {
+        // Encode the number of arguments in the method name
+        String methodIndexName = String.format("%1$s.%2$s#%3$d", targetClass.getSimpleName(),
+                methodName, nArgs);
+        synchronized (sMethodIndexLock) {
+            Long methodId = METHOD_NAME_TO_ID.get(methodIndexName);
+
+            if (methodId != null) {
+                // The method was already registered
+                return methodId;
+            }
+
+            Class[] args = new Class[nArgs];
+            Method method = null;
+            for (Class typeVariant : types) {
+                for (int i = 0; i < nArgs; i++) {
+                    args[i] = typeVariant;
+                }
+                try {
+                    method = targetClass.getDeclaredMethod(methodName, args);
+                } catch (NoSuchMethodException ignore) {
+                }
+            }
+
+            if (method != null) {
+                methodId = sNextId++;
+                ID_TO_METHOD.put(methodId, method);
+                METHOD_NAME_TO_ID.put(methodIndexName, methodId);
+
+                return methodId;
+            }
+        }
+
+        // Method not found
+        return 0;
+    }
+
+    private static void callMethod(Object target, long methodID, Object... args) {
+        Method method = ID_TO_METHOD.get(methodID);
+        assert method != null;
+
+        try {
+            method.setAccessible(true);
+            method.invoke(target, args);
+        } catch (IllegalAccessException | InvocationTargetException e) {
+            Bridge.getLog().error(null, "Unable to update property during animation", e, null);
+        }
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nGetIntMethod(Class<?> targetClass, String methodName) {
+        return nGetMultipleIntMethod(targetClass, methodName, 1);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nGetFloatMethod(Class<?> targetClass, String methodName) {
+        return nGetMultipleFloatMethod(targetClass, methodName, 1);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nGetMultipleIntMethod(Class<?> targetClass, String methodName,
+            int numParams) {
+        return registerMethod(targetClass, methodName, INTEGER_VARIANTS, numParams);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static long nGetMultipleFloatMethod(Class<?> targetClass, String methodName,
+            int numParams) {
+        return registerMethod(targetClass, methodName, FLOAT_VARIANTS, numParams);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCallIntMethod(Object target, long methodID, int arg) {
+        callMethod(target, methodID, arg);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCallFloatMethod(Object target, long methodID, float arg) {
+        callMethod(target, methodID, arg);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCallTwoIntMethod(Object target, long methodID, int arg1,
+            int arg2) {
+        callMethod(target, methodID, arg1, arg2);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCallFourIntMethod(Object target, long methodID, int arg1,
+            int arg2, int arg3, int arg4) {
+        callMethod(target, methodID, arg1, arg2, arg3, arg4);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCallMultipleIntMethod(Object target, long methodID,
+            int[] args) {
+        assert args != null;
+
+        // Box parameters
+        Object[] params = new Object[args.length];
+        for (int i = 0; i < args.length; i++) {
+            params[i] = args;
+        }
+        callMethod(target, methodID, params);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCallTwoFloatMethod(Object target, long methodID, float arg1,
+            float arg2) {
+        callMethod(target, methodID, arg1, arg2);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCallFourFloatMethod(Object target, long methodID, float arg1,
+            float arg2, float arg3, float arg4) {
+        callMethod(target, methodID, arg1, arg2, arg3, arg4);
+    }
+
+    @LayoutlibDelegate
+    /*package*/ static void nCallMultipleFloatMethod(Object target, long methodID,
+            float[] args) {
+        assert args != null;
+
+        // Box parameters
+        Object[] params = new Object[args.length];
+        for (int i = 0; i < args.length; i++) {
+            params[i] = args;
+        }
+        callMethod(target, methodID, params);
+    }
+}
diff --git a/android/animation/RectEvaluator.java b/android/animation/RectEvaluator.java
new file mode 100644
index 0000000..23eb766
--- /dev/null
+++ b/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/animation/RevealAnimator.java b/android/animation/RevealAnimator.java
new file mode 100644
index 0000000..0f85f49
--- /dev/null
+++ b/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/animation/StateListAnimator.java b/android/animation/StateListAnimator.java
new file mode 100644
index 0000000..b6d6910
--- /dev/null
+++ b/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/animation/TimeAnimator.java b/android/animation/TimeAnimator.java
new file mode 100644
index 0000000..113a21f
--- /dev/null
+++ b/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/animation/TimeInterpolator.java b/android/animation/TimeInterpolator.java
new file mode 100644
index 0000000..0f5d8bf
--- /dev/null
+++ b/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/animation/TypeConverter.java b/android/animation/TypeConverter.java
new file mode 100644
index 0000000..9ead2ad
--- /dev/null
+++ b/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/animation/TypeEvaluator.java b/android/animation/TypeEvaluator.java
new file mode 100644
index 0000000..429c435
--- /dev/null
+++ b/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/animation/ValueAnimator.java b/android/animation/ValueAnimator.java
new file mode 100644
index 0000000..ebb03e7
--- /dev/null
+++ b/android/animation/ValueAnimator.java
@@ -0,0 +1,1687 @@
+/*
+ * 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.IntDef;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Looper;
+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.util.ArrayList;
+import java.util.HashMap;
+
+/**
+ * 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;
+
+    /**
+     * 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;
+
+    /**
+     * 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;
+
+    /**
+     * 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().
+     */
+    private boolean mStartListenersCalled = 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;
+
+    /**
+     * 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
+     */
+    @TestApi
+    public static void setDurationScale(float durationScale) {
+        sDurationScale = durationScale;
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public static float getDurationScale() {
+        return sDurationScale;
+    }
+
+    /**
+     * 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<String, PropertyValuesHolder>(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) {
+            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);
+        }
+    }
+
+    private void notifyStartListeners() {
+        if (mListeners != null && !mStartListenersCalled) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationStart(this, mReversing);
+            }
+        }
+        mStartListenersCalled = true;
+    }
+
+    /**
+     * 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) && mListeners != null) {
+            if (!mRunning) {
+                // If it's not yet running, then start listeners weren't called. Call them now.
+                notifyStartListeners();
+            }
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            for (AnimatorListener listener : tmpListeners) {
+                listener.onAnimationCancel(this);
+            }
+        }
+        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();
+        }
+        mRunning = false;
+        mStarted = false;
+        mStartListenersCalled = false;
+        mLastFrameTime = -1;
+        mFirstFrameTime = -1;
+        mStartTime = -1;
+        if (notify && mListeners != null) {
+            ArrayList<AnimatorListener> tmpListeners =
+                    (ArrayList<AnimatorListener>) mListeners.clone();
+            int numListeners = tmpListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                tmpListeners.get(i).onAnimationEnd(this, 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;
+        }
+        if (mListeners != null) {
+            notifyStartListeners();
+        }
+    }
+
+    /**
+     * 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
+                if (mListeners != null) {
+                    int numListeners = mListeners.size();
+                    for (int i = 0; i < numListeners; ++i) {
+                        mListeners.get(i).onAnimationRepeat(this);
+                    }
+                }
+            } 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 animateBasedOnPlayTime(long currentPlayTime, long lastPlayTime, boolean inReverse) {
+        if (currentPlayTime < 0 || lastPlayTime < 0) {
+            throw new UnsupportedOperationException("Error: Play time should never be negative.");
+        }
+
+        initAnimation();
+        // Check whether repeat callback is needed only when repeat count is non-zero
+        if (mRepeatCount > 0) {
+            int iteration = (int) (currentPlayTime / mDuration);
+            int lastIteration = (int) (lastPlayTime / mDuration);
+
+            // Clamp iteration to [0, mRepeatCount]
+            iteration = Math.min(iteration, mRepeatCount);
+            lastIteration = Math.min(lastIteration, mRepeatCount);
+
+            if (iteration != lastIteration) {
+                if (mListeners != null) {
+                    int numListeners = mListeners.size();
+                    for (int i = 0; i < numListeners; ++i) {
+                        mListeners.get(i).onAnimationRepeat(this);
+                    }
+                }
+            }
+        }
+
+        if (mRepeatCount != INFINITE && currentPlayTime >= (mRepeatCount + 1) * mDuration) {
+            skipToEndValue(inReverse);
+        } else {
+            // Find the current fraction:
+            float fraction = currentPlayTime / (float) mDuration;
+            fraction = getCurrentIterationFraction(fraction, inReverse);
+            animateValue(fraction);
+        }
+    }
+
+    /**
+     * 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) {
+        fraction = mInterpolator.getInterpolation(fraction);
+        mCurrentFraction = fraction;
+        int numValues = mValues.length;
+        for (int i = 0; i < numValues; ++i) {
+            mValues[i].calculateValue(fraction);
+        }
+        if (mUpdateListeners != null) {
+            int numListeners = mUpdateListeners.size();
+            for (int i = 0; i < numListeners; ++i) {
+                mUpdateListeners.get(i).onAnimationUpdate(this);
+            }
+        }
+    }
+
+    @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.mStartListenersCalled = 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(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 AnimationHandler.getInstance();
+    }
+}
diff --git a/android/annotation/AnimRes.java b/android/annotation/AnimRes.java
new file mode 100644
index 0000000..56f8acf
--- /dev/null
+++ b/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/annotation/AnimatorRes.java b/android/annotation/AnimatorRes.java
new file mode 100644
index 0000000..cd4c189
--- /dev/null
+++ b/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/annotation/AnyRes.java b/android/annotation/AnyRes.java
new file mode 100644
index 0000000..44411a0
--- /dev/null
+++ b/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/annotation/AnyThread.java b/android/annotation/AnyThread.java
new file mode 100644
index 0000000..ee36a42
--- /dev/null
+++ b/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/annotation/AppIdInt.java b/android/annotation/AppIdInt.java
new file mode 100644
index 0000000..29838dd
--- /dev/null
+++ b/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/annotation/ArrayRes.java b/android/annotation/ArrayRes.java
new file mode 100644
index 0000000..1407af1
--- /dev/null
+++ b/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/annotation/AttrRes.java b/android/annotation/AttrRes.java
new file mode 100644
index 0000000..285b80c
--- /dev/null
+++ b/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/annotation/BinderThread.java b/android/annotation/BinderThread.java
new file mode 100644
index 0000000..ca5e14c
--- /dev/null
+++ b/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/annotation/BoolRes.java b/android/annotation/BoolRes.java
new file mode 100644
index 0000000..f50785b
--- /dev/null
+++ b/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/annotation/BroadcastBehavior.java b/android/annotation/BroadcastBehavior.java
new file mode 100644
index 0000000..70d82cb
--- /dev/null
+++ b/android/annotation/BroadcastBehavior.java
@@ -0,0 +1,61 @@
+/*
+ * 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 android.content.Intent;
+
+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 Intent#setPackage(String)
+     * @see Intent#setComponent(android.content.ComponentName)
+     */
+    boolean explicitOnly() default false;
+
+    /**
+     * This broadcast will only be delivered to registered receivers.
+     *
+     * @see Intent#FLAG_RECEIVER_REGISTERED_ONLY
+     */
+    boolean registeredOnly() default false;
+
+    /**
+     * This broadcast will include all {@code AndroidManifest.xml} receivers
+     * regardless of process state.
+     *
+     * @see 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/annotation/BytesLong.java b/android/annotation/BytesLong.java
new file mode 100644
index 0000000..f5e1a9c
--- /dev/null
+++ b/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/annotation/CallSuper.java b/android/annotation/CallSuper.java
new file mode 100644
index 0000000..c16b511
--- /dev/null
+++ b/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/annotation/CallbackExecutor.java b/android/annotation/CallbackExecutor.java
new file mode 100644
index 0000000..5671a3d
--- /dev/null
+++ b/android/annotation/CallbackExecutor.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.content.Context;
+import android.os.AsyncTask;
+
+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 Context#getMainExecutor()}. To
+ *           dispatch events through a shared thread pool, you can use
+ *           {@link AsyncTask#THREAD_POOL_EXECUTOR}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target(PARAMETER)
+public @interface CallbackExecutor {
+}
diff --git a/android/annotation/CheckResult.java b/android/annotation/CheckResult.java
new file mode 100644
index 0000000..97d031a
--- /dev/null
+++ b/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/annotation/ColorInt.java b/android/annotation/ColorInt.java
new file mode 100644
index 0000000..4671b1b
--- /dev/null
+++ b/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/annotation/ColorLong.java b/android/annotation/ColorLong.java
new file mode 100644
index 0000000..9b19c76
--- /dev/null
+++ b/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/annotation/ColorRes.java b/android/annotation/ColorRes.java
new file mode 100644
index 0000000..061faa0
--- /dev/null
+++ b/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/annotation/Condemned.java b/android/annotation/Condemned.java
new file mode 100644
index 0000000..186409b
--- /dev/null
+++ b/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/annotation/CurrentTimeMillisLong.java b/android/annotation/CurrentTimeMillisLong.java
new file mode 100644
index 0000000..355bb5a
--- /dev/null
+++ b/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/annotation/CurrentTimeSecondsLong.java b/android/annotation/CurrentTimeSecondsLong.java
new file mode 100644
index 0000000..2b4ffd7
--- /dev/null
+++ b/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/annotation/DimenRes.java b/android/annotation/DimenRes.java
new file mode 100644
index 0000000..02ae00c
--- /dev/null
+++ b/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/annotation/Dimension.java b/android/annotation/Dimension.java
new file mode 100644
index 0000000..5f705ad
--- /dev/null
+++ b/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/annotation/DrawableRes.java b/android/annotation/DrawableRes.java
new file mode 100644
index 0000000..ebefa1d
--- /dev/null
+++ b/android/annotation/DrawableRes.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 drawable resource reference (e.g. {@link android.R.attr#alertDialogIcon}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface DrawableRes {
+}
diff --git a/android/annotation/DurationMillisLong.java b/android/annotation/DurationMillisLong.java
new file mode 100644
index 0000000..ce77532
--- /dev/null
+++ b/android/annotation/DurationMillisLong.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 duration in milliseconds.
+ * @paramDoc Value is a non-negative duration in milliseconds.
+ * @returnDoc Value is a non-negative duration in milliseconds.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface DurationMillisLong {
+}
diff --git a/android/annotation/ElapsedRealtimeLong.java b/android/annotation/ElapsedRealtimeLong.java
new file mode 100644
index 0000000..f77ff72
--- /dev/null
+++ b/android/annotation/ElapsedRealtimeLong.java
@@ -0,0 +1,41 @@
+/*
+ * 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 android.os.SystemClock;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * @memberDoc Value is a non-negative timestamp in the
+ *            {@link SystemClock#elapsedRealtime()} time base.
+ * @paramDoc Value is a non-negative timestamp in the
+ *           {@link SystemClock#elapsedRealtime()} time base.
+ * @returnDoc Value is a non-negative timestamp in the
+ *            {@link SystemClock#elapsedRealtime()} time base.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface ElapsedRealtimeLong {
+}
diff --git a/android/annotation/FloatRange.java b/android/annotation/FloatRange.java
new file mode 100644
index 0000000..05b5168
--- /dev/null
+++ b/android/annotation/FloatRange.java
@@ -0,0 +1,55 @@
+/*
+ * 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 should be a float or double in the given range
+ * <p>
+ * Example:
+ * <pre><code>
+ *  &#64;FloatRange(from=0.0,to=1.0)
+ *  public float getAlpha() {
+ *      ...
+ *  }
+ * </code></pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE})
+public @interface FloatRange {
+    /** Smallest value. Whether it is inclusive or not is determined
+     * by {@link #fromInclusive} */
+    double from() default Double.NEGATIVE_INFINITY;
+    /** Largest value. Whether it is inclusive or not is determined
+     * by {@link #toInclusive} */
+    double to() default Double.POSITIVE_INFINITY;
+
+    /** Whether the from value is included in the range */
+    boolean fromInclusive() default true;
+
+    /** Whether the to value is included in the range */
+    boolean toInclusive() default true;
+}
\ No newline at end of file
diff --git a/android/annotation/FontRes.java b/android/annotation/FontRes.java
new file mode 100644
index 0000000..dbacb58
--- /dev/null
+++ b/android/annotation/FontRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.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 Font resource reference (e.g. R.font.myfont).
+ *
+ * @hide
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface FontRes {
+}
diff --git a/android/annotation/FractionRes.java b/android/annotation/FractionRes.java
new file mode 100644
index 0000000..fd84d3e
--- /dev/null
+++ b/android/annotation/FractionRes.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 fraction resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface FractionRes {
+}
diff --git a/android/annotation/HalfFloat.java b/android/annotation/HalfFloat.java
new file mode 100644
index 0000000..256008c
--- /dev/null
+++ b/android/annotation/HalfFloat.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 half-precision floating point
+ * value. Such values are stored in short data types and can be manipulated with
+ * the {@link android.util.Half} class. If applied to an array of short, every
+ * element in the array represents a half-precision float.</p>
+ *
+ * <p>Example:</p>
+ *
+ * <pre>{@code
+ * public abstract void setPosition(@HalfFloat short x, @HalfFloat short y, @HalfFloat short z);
+ * }</pre>
+ *
+ * @see android.util.Half
+ * @see android.util.Half#toHalf(float)
+ * @see android.util.Half#toFloat(short)
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER, METHOD, LOCAL_VARIABLE, FIELD})
+public @interface HalfFloat {
+}
diff --git a/android/annotation/IdRes.java b/android/annotation/IdRes.java
new file mode 100644
index 0000000..b286965
--- /dev/null
+++ b/android/annotation/IdRes.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 id resource reference (e.g. {@link android.R.id#copy}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface IdRes {
+}
diff --git a/android/annotation/IntDef.java b/android/annotation/IntDef.java
new file mode 100644
index 0000000..f84a676
--- /dev/null
+++ b/android/annotation/IntDef.java
@@ -0,0 +1,64 @@
+/*
+ * 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.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated element of integer type, represents
+ * a logical type and that its value should be one of the explicitly
+ * named constants. If the {@link #flag()} attribute is set to true,
+ * multiple constants can be combined.
+ * <p>
+ * <pre><code>
+ *  &#64;Retention(SOURCE)
+ *  &#64;IntDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ *  public @interface NavigationMode {}
+ *  public static final int NAVIGATION_MODE_STANDARD = 0;
+ *  public static final int NAVIGATION_MODE_LIST = 1;
+ *  public static final int NAVIGATION_MODE_TABS = 2;
+ *  ...
+ *  public abstract void setNavigationMode(@NavigationMode int mode);
+ *  &#64;NavigationMode
+ *  public abstract int getNavigationMode();
+ * </code></pre>
+ * For a flag, set the flag attribute:
+ * <pre><code>
+ *  &#64;IntDef(
+ *      flag = true,
+ *      value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * </code></pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE})
+public @interface IntDef {
+    /** Defines the constant prefix for this element */
+    String[] prefix() default {};
+    /** Defines the constant suffix for this element */
+    String[] suffix() default {};
+
+    /** Defines the allowed constants for this element */
+    int[] value() default {};
+
+    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
+    boolean flag() default false;
+}
diff --git a/android/annotation/IntRange.java b/android/annotation/IntRange.java
new file mode 100644
index 0000000..c043e2d
--- /dev/null
+++ b/android/annotation/IntRange.java
@@ -0,0 +1,48 @@
+/*
+ * 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.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 the annotated element should be an int or long in the given range
+ * <p>
+ * Example:
+ * <pre><code>
+ *  &#64;IntRange(from=0,to=255)
+ *  public int getAlpha() {
+ *      ...
+ *  }
+ * </code></pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
+public @interface IntRange {
+    /** Smallest value, inclusive */
+    long from() default Long.MIN_VALUE;
+    /** Largest value, inclusive */
+    long to() default Long.MAX_VALUE;
+}
\ No newline at end of file
diff --git a/android/annotation/IntegerRes.java b/android/annotation/IntegerRes.java
new file mode 100644
index 0000000..5313f4a
--- /dev/null
+++ b/android/annotation/IntegerRes.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 integer resource reference (e.g. {@link android.R.integer#config_shortAnimTime}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface IntegerRes {
+}
diff --git a/android/annotation/InterpolatorRes.java b/android/annotation/InterpolatorRes.java
new file mode 100644
index 0000000..8877a5f
--- /dev/null
+++ b/android/annotation/InterpolatorRes.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 interpolator resource reference (e.g. {@link android.R.interpolator#cycle}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface InterpolatorRes {
+}
diff --git a/android/annotation/LayoutRes.java b/android/annotation/LayoutRes.java
new file mode 100644
index 0000000..15ba86f
--- /dev/null
+++ b/android/annotation/LayoutRes.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 layout resource reference (e.g. {@link android.R.layout#list_content}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface LayoutRes {
+}
diff --git a/android/annotation/LongDef.java b/android/annotation/LongDef.java
new file mode 100644
index 0000000..8723eef
--- /dev/null
+++ b/android/annotation/LongDef.java
@@ -0,0 +1,62 @@
+/*
+ * 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.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated long element represents
+ * a logical type and that its value should be one of the explicitly
+ * named constants. If the {@link #flag()} attribute is set to true,
+ * multiple constants can be combined.
+ * <p>
+ * <pre><code>
+ *  &#64;Retention(SOURCE)
+ *  &#64;LongDef({NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ *  public @interface NavigationMode {}
+ *  public static final long NAVIGATION_MODE_STANDARD = 0;
+ *  public static final long NAVIGATION_MODE_LIST = 1;
+ *  public static final long NAVIGATION_MODE_TABS = 2;
+ *  ...
+ *  public abstract void setNavigationMode(@NavigationMode long mode);
+ *  &#64;NavigationMode
+ *  public abstract long getNavigationMode();
+ * </code></pre>
+ * For a flag, set the flag attribute:
+ * <pre><code>
+ *  &#64;LongDef(
+ *      flag = true,
+ *      value = {NAVIGATION_MODE_STANDARD, NAVIGATION_MODE_LIST, NAVIGATION_MODE_TABS})
+ * </code></pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE})
+public @interface LongDef {
+    /** Defines the constant prefix for this element */
+    String[] prefix() default "";
+
+    /** Defines the allowed constants for this element */
+    long[] value() default {};
+
+    /** Defines whether the constants can be used as a flag, or just as an enum (the default) */
+    boolean flag() default false;
+}
diff --git a/android/annotation/MainThread.java b/android/annotation/MainThread.java
new file mode 100644
index 0000000..556fdb4
--- /dev/null
+++ b/android/annotation/MainThread.java
@@ -0,0 +1,50 @@
+/*
+ * 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.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 android.os.Looper;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated method should only be called on the main thread.
+ * If the annotated element is a class, then all methods in the class should be
+ * called on the main thread.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * <code>
+ *  &#64;MainThread
+ *  public void deliverResult(D data) { ... }
+ * </code>
+ * </pre>
+ *
+ * @memberDoc This method must be called from the
+ *            {@linkplain Looper#getMainLooper() main thread} of your app.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface MainThread {
+}
diff --git a/android/annotation/MenuRes.java b/android/annotation/MenuRes.java
new file mode 100644
index 0000000..b6dcc46
--- /dev/null
+++ b/android/annotation/MenuRes.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 menu resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface MenuRes {
+}
diff --git a/android/annotation/NavigationRes.java b/android/annotation/NavigationRes.java
new file mode 100644
index 0000000..3af5ecf
--- /dev/null
+++ b/android/annotation/NavigationRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.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 navigation resource reference (e.g. {@code R.navigation.flow}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface NavigationRes {
+}
diff --git a/android/annotation/NonNull.java b/android/annotation/NonNull.java
new file mode 100644
index 0000000..927f997
--- /dev/null
+++ b/android/annotation/NonNull.java
@@ -0,0 +1,38 @@
+/*
+ * 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.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 a parameter, field or method return value can never be null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ *
+ * @paramDoc This value must never be {@code null}.
+ * @returnDoc This value will never be {@code null}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface NonNull {
+}
diff --git a/android/annotation/Nullable.java b/android/annotation/Nullable.java
new file mode 100644
index 0000000..b60170b
--- /dev/null
+++ b/android/annotation/Nullable.java
@@ -0,0 +1,45 @@
+/*
+ * 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.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 a parameter, field or method return value can be null.
+ * <p>
+ * When decorating a method call parameter, this denotes that the parameter can
+ * legitimately be null and the method will gracefully deal with it. Typically
+ * used on optional parameters.
+ * <p>
+ * When decorating a method, this denotes the method might legitimately return
+ * null.
+ * <p>
+ * This is a marker annotation and it has no specific attributes.
+ *
+ * @paramDoc This value may be {@code null}.
+ * @returnDoc This value may be {@code null}.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface Nullable {
+}
diff --git a/android/annotation/PluralsRes.java b/android/annotation/PluralsRes.java
new file mode 100644
index 0000000..31ac729
--- /dev/null
+++ b/android/annotation/PluralsRes.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 plurals resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface PluralsRes {
+}
diff --git a/android/annotation/Px.java b/android/annotation/Px.java
new file mode 100644
index 0000000..cec7f80
--- /dev/null
+++ b/android/annotation/Px.java
@@ -0,0 +1,42 @@
+/*
+ * 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.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 pixel dimension.
+ *
+ * @memberDoc This units of this value are pixels.
+ * @paramDoc This units of this value are pixels.
+ * @returnDoc This units of this value are pixels.
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+@Dimension(unit = Dimension.PX)
+public @interface Px {
+}
diff --git a/android/annotation/RawRes.java b/android/annotation/RawRes.java
new file mode 100644
index 0000000..39970b3
--- /dev/null
+++ b/android/annotation/RawRes.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 raw resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface RawRes {
+}
diff --git a/android/annotation/RequiresFeature.java b/android/annotation/RequiresFeature.java
new file mode 100644
index 0000000..fc93f03
--- /dev/null
+++ b/android/annotation/RequiresFeature.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.content.pm.PackageManager;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated element requires one or more device features. This
+ * is used to auto-generate documentation.
+ *
+ * @see PackageManager#hasSystemFeature(String)
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({TYPE,FIELD,METHOD,CONSTRUCTOR})
+public @interface RequiresFeature {
+    /**
+     * The name of the device feature that is required.
+     *
+     * @see PackageManager#hasSystemFeature(String)
+     */
+    String value();
+}
diff --git a/android/annotation/RequiresPermission.java b/android/annotation/RequiresPermission.java
new file mode 100644
index 0000000..59d419f
--- /dev/null
+++ b/android/annotation/RequiresPermission.java
@@ -0,0 +1,137 @@
+/*
+ * 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 android.content.Intent;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+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 the annotated element requires (or may require) one or more permissions.
+ * <p/>
+ * Example of requiring a single permission:
+ * <pre>{@code
+ *   {@literal @}RequiresPermission(Manifest.permission.SET_WALLPAPER)
+ *   public abstract void setWallpaper(Bitmap bitmap) throws IOException;
+ *
+ *   {@literal @}RequiresPermission(ACCESS_COARSE_LOCATION)
+ *   public abstract Location getLastKnownLocation(String provider);
+ * }</pre>
+ * Example of requiring at least one permission from a set:
+ * <pre>{@code
+ *   {@literal @}RequiresPermission(anyOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ *   public abstract Location getLastKnownLocation(String provider);
+ * }</pre>
+ * Example of requiring multiple permissions:
+ * <pre>{@code
+ *   {@literal @}RequiresPermission(allOf = {ACCESS_COARSE_LOCATION, ACCESS_FINE_LOCATION})
+ *   public abstract Location getLastKnownLocation(String provider);
+ * }</pre>
+ * Example of requiring separate read and write permissions for a content provider:
+ * <pre>{@code
+ *   {@literal @}RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
+ *   {@literal @}RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
+ *   public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
+ * }</pre>
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter. For example, consider
+ * {@link android.app.Activity#startActivity(Intent)}:
+ * <pre>{@code
+ *   public void startActivity(@RequiresPermission Intent intent) { ... }
+ * }</pre>
+ * Notice how there are no actual permission names listed in the annotation. The actual
+ * permissions required will depend on the particular intent passed in. For example,
+ * the code may look like this:
+ * <pre>{@code
+ *   Intent intent = new Intent(Intent.ACTION_CALL);
+ *   startActivity(intent);
+ * }</pre>
+ * and the actual permission requirement for this particular intent is described on
+ * the Intent name itself:
+ * <pre>{@code
+ *   {@literal @}RequiresPermission(Manifest.permission.CALL_PHONE)
+ *   public static final String ACTION_CALL = "android.intent.action.CALL";
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
+public @interface RequiresPermission {
+    /**
+     * The name of the permission that is required, if precisely one permission
+     * is required. If more than one permission is required, specify either
+     * {@link #allOf()} or {@link #anyOf()} instead.
+     * <p>
+     * If specified, {@link #anyOf()} and {@link #allOf()} must both be null.
+     */
+    String value() default "";
+
+    /**
+     * Specifies a list of permission names that are all required.
+     * <p>
+     * If specified, {@link #anyOf()} and {@link #value()} must both be null.
+     */
+    String[] allOf() default {};
+
+    /**
+     * Specifies a list of permission names where at least one is required
+     * <p>
+     * If specified, {@link #allOf()} and {@link #value()} must both be null.
+     */
+    String[] anyOf() default {};
+
+    /**
+     * If true, the permission may not be required in all cases (e.g. it may only be
+     * enforced on certain platforms, or for certain call parameters, etc.
+     */
+    boolean conditional() default false;
+
+    /**
+     * Specifies that the given permission is required for read operations.
+     * <p>
+     * When specified on a parameter, the annotation indicates that the method requires
+     * a permission which depends on the value of the parameter (and typically
+     * the corresponding field passed in will be one of a set of constants which have
+     * been annotated with a <code>@RequiresPermission</code> annotation.)
+     */
+    @Target({FIELD, METHOD, PARAMETER})
+    @interface Read {
+        RequiresPermission value() default @RequiresPermission;
+    }
+
+    /**
+     * Specifies that the given permission is required for write operations.
+     * <p>
+     * When specified on a parameter, the annotation indicates that the method requires
+     * a permission which depends on the value of the parameter (and typically
+     * the corresponding field passed in will be one of a set of constants which have
+     * been annotated with a <code>@RequiresPermission</code> annotation.)
+     */
+    @Target({FIELD, METHOD, PARAMETER})
+    @interface Write {
+        RequiresPermission value() default @RequiresPermission;
+    }
+}
diff --git a/android/annotation/SdkConstant.java b/android/annotation/SdkConstant.java
new file mode 100644
index 0000000..0a53186
--- /dev/null
+++ b/android/annotation/SdkConstant.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates a constant field value should be exported to be used in the SDK tools.
+ * @hide
+ */
+@Target({ ElementType.FIELD })
+@Retention(RetentionPolicy.SOURCE)
+public @interface SdkConstant {
+    public static enum SdkConstantType {
+        ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE;
+    }
+
+    SdkConstantType value();
+}
diff --git a/android/annotation/Size.java b/android/annotation/Size.java
new file mode 100644
index 0000000..7c3e70f
--- /dev/null
+++ b/android/annotation/Size.java
@@ -0,0 +1,52 @@
+/*
+ * 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 should have a given size or length.
+ * Note that "-1" means "unset". Typically used with a parameter or
+ * return value of type array or collection.
+ * <p>
+ * Example:
+ * <pre>{@code
+ *  public void getLocationInWindow(@Size(2) int[] location) {
+ *      ...
+ *  }
+ * }</pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({PARAMETER,LOCAL_VARIABLE,METHOD,FIELD})
+public @interface Size {
+    /** An exact size (or -1 if not specified) */
+    long value() default -1;
+    /** A minimum size, inclusive */
+    long min() default Long.MIN_VALUE;
+    /** A maximum size, inclusive */
+    long max() default Long.MAX_VALUE;
+    /** The size must be a multiple of this factor */
+    long multiple() default 1;
+}
\ No newline at end of file
diff --git a/android/annotation/StringDef.java b/android/annotation/StringDef.java
new file mode 100644
index 0000000..a37535b
--- /dev/null
+++ b/android/annotation/StringDef.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.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+/**
+ * Denotes that the annotated String element, represents a logical
+ * type and that its value should be one of the explicitly named constants.
+ * <p>
+ * Example:
+ * <pre><code>
+ *  &#64;Retention(SOURCE)
+ *  &#64;StringDef({
+ *     POWER_SERVICE,
+ *     WINDOW_SERVICE,
+ *     LAYOUT_INFLATER_SERVICE
+ *  })
+ *  public @interface ServiceName {}
+ *  public static final String POWER_SERVICE = "power";
+ *  public static final String WINDOW_SERVICE = "window";
+ *  public static final String LAYOUT_INFLATER_SERVICE = "layout_inflater";
+ *  ...
+ *  public abstract Object getSystemService(@ServiceName String name);
+ * </code></pre>
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({ANNOTATION_TYPE})
+public @interface StringDef {
+    /** Defines the constant prefix for this element */
+    String[] prefix() default {};
+    /** Defines the constant suffix for this element */
+    String[] suffix() default {};
+
+    /** Defines the allowed constants for this element */
+    String[] value() default {};
+}
diff --git a/android/annotation/StringRes.java b/android/annotation/StringRes.java
new file mode 100644
index 0000000..190b68a
--- /dev/null
+++ b/android/annotation/StringRes.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 String resource reference (e.g. {@link android.R.string#ok}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface StringRes {
+}
diff --git a/android/annotation/StyleRes.java b/android/annotation/StyleRes.java
new file mode 100644
index 0000000..4453b8d
--- /dev/null
+++ b/android/annotation/StyleRes.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 a integer parameter, field or method return value is expected
+ * to be a style resource reference (e.g. {@link android.R.style#TextAppearance}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface StyleRes {
+}
diff --git a/android/annotation/StyleableRes.java b/android/annotation/StyleableRes.java
new file mode 100644
index 0000000..3c1895e
--- /dev/null
+++ b/android/annotation/StyleableRes.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 a integer parameter, field or method return value is expected
+ * to be a styleable resource reference (e.g. {@link android.R.styleable#TextView_text}).
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface StyleableRes {
+}
diff --git a/android/annotation/SuppressAutoDoc.java b/android/annotation/SuppressAutoDoc.java
new file mode 100644
index 0000000..e34e03b
--- /dev/null
+++ b/android/annotation/SuppressAutoDoc.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.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.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 any automatically generated documentation should be suppressed
+ * for the annotated method, parameter, or field.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+public @interface SuppressAutoDoc {
+}
diff --git a/android/annotation/SuppressLint.java b/android/annotation/SuppressLint.java
new file mode 100644
index 0000000..2d3456b
--- /dev/null
+++ b/android/annotation/SuppressLint.java
@@ -0,0 +1,38 @@
+/*
+ * 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.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.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should ignore the specified warnings for the annotated element. */
+@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
+@Retention(RetentionPolicy.CLASS)
+public @interface SuppressLint {
+    /**
+     * The set of warnings (identified by the lint issue id) that should be
+     * ignored by lint. It is not an error to specify an unrecognized name.
+     */
+    String[] value();
+}
diff --git a/android/annotation/SystemApi.java b/android/annotation/SystemApi.java
new file mode 100644
index 0000000..e96ff01
--- /dev/null
+++ b/android/annotation/SystemApi.java
@@ -0,0 +1,44 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API is exposed for use by bundled system applications.
+ * <p>
+ * These APIs are not guaranteed to remain consistent release-to-release,
+ * and are not for use by apps linking against the Android SDK.
+ * </p><p>
+ * This annotation should only appear on API that is already marked <pre>@hide</pre>.
+ * </p>
+ *
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface SystemApi {
+}
diff --git a/android/annotation/SystemService.java b/android/annotation/SystemService.java
new file mode 100644
index 0000000..0c5d15e
--- /dev/null
+++ b/android/annotation/SystemService.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.TYPE;
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.content.Context;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Description of a system service available through
+ * {@link Context#getSystemService(Class)}. This is used to auto-generate
+ * documentation explaining how to obtain a reference to the service.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target(TYPE)
+public @interface SystemService {
+    /**
+     * The string name of the system service that can be passed to
+     * {@link Context#getSystemService(String)}.
+     *
+     * @see Context#getSystemServiceName(Class)
+     */
+    String value();
+}
diff --git a/android/annotation/TargetApi.java b/android/annotation/TargetApi.java
new file mode 100644
index 0000000..975318e
--- /dev/null
+++ b/android/annotation/TargetApi.java
@@ -0,0 +1,36 @@
+/*
+ * 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.annotation;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/** Indicates that Lint should treat this type as targeting a given API level, no matter what the
+    project target is. */
+@Target({TYPE, METHOD, CONSTRUCTOR, FIELD})
+@Retention(RetentionPolicy.CLASS)
+public @interface TargetApi {
+    /**
+     * This sets the target api level for the type..
+     */
+    int value();
+}
diff --git a/android/annotation/TestApi.java b/android/annotation/TestApi.java
new file mode 100644
index 0000000..0e9ed37
--- /dev/null
+++ b/android/annotation/TestApi.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 static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PACKAGE;
+import static java.lang.annotation.ElementType.TYPE;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates an API is exposed for use by CTS.
+ * <p>
+ * These APIs are not guaranteed to remain consistent release-to-release,
+ * and are not for use by apps linking against the Android SDK.
+ * </p>
+ *
+ * @hide
+ */
+@Target({TYPE, FIELD, METHOD, CONSTRUCTOR, ANNOTATION_TYPE, PACKAGE})
+@Retention(RetentionPolicy.SOURCE)
+public @interface TestApi {
+}
diff --git a/android/annotation/TransitionRes.java b/android/annotation/TransitionRes.java
new file mode 100644
index 0000000..06bac74
--- /dev/null
+++ b/android/annotation/TransitionRes.java
@@ -0,0 +1,37 @@
+/*
+ * 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.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 transition resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface TransitionRes {
+}
diff --git a/android/annotation/UiThread.java b/android/annotation/UiThread.java
new file mode 100644
index 0000000..6d7eedc
--- /dev/null
+++ b/android/annotation/UiThread.java
@@ -0,0 +1,51 @@
+/*
+ * 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.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 android.os.Looper;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Denotes that the annotated method or constructor should only be called on the
+ * UI thread. If the annotated element is a class, then all methods in the class
+ * should be called on the UI thread.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * <code>
+ *  &#64;UiThread
+ *  public abstract void setText(@NonNull String text) { ... }
+ * </code>
+ * </pre>
+ *
+ * @memberDoc This method must be called on the thread that originally created
+ *            this UI element. This is typically the
+ *            {@linkplain Looper#getMainLooper() main thread} of your app.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface UiThread {
+}
diff --git a/android/annotation/UnsupportedAppUsage.java b/android/annotation/UnsupportedAppUsage.java
new file mode 100644
index 0000000..ac3daaf
--- /dev/null
+++ b/android/annotation/UnsupportedAppUsage.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Repeatable;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+/**
+ * Indicates that this non-SDK interface is used by apps. A non-SDK interface is a
+ * class member (field or method) that is not part of the public SDK. Since the
+ * member is not part of the SDK, usage by apps is not supported.
+ *
+ * <h2>If you are an Android App developer</h2>
+ *
+ * This annotation indicates that you may be able to access the member, but that
+ * this access is discouraged and not supported by Android. If there is a value
+ * for {@link #maxTargetSdk()} on the annotation, access will be restricted based
+ * on the {@code targetSdkVersion} value set in your manifest.
+ *
+ * <p>Fields and methods annotated with this are likely to be restricted, changed
+ * or removed in future Android releases. If you rely on these members for
+ * functionality that is not otherwise supported by Android, consider filing a
+ * <a href="http://g.co/dev/appcompat">feature request</a>.
+ *
+ * <h2>If you are an Android OS developer</h2>
+ *
+ * This annotation acts as a heads up that changing a given method or field
+ * may affect apps, potentially breaking them when the next Android version is
+ * released. In some cases, for members that are heavily used, this annotation
+ * may imply restrictions on changes to the member.
+ *
+ * <p>This annotation also results in access to the member being permitted by the
+ * runtime, with a warning being generated in debug builds. Which apps can access
+ * the member is determined by the value of {@link #maxTargetSdk()}.
+ *
+ * <p>For more details, see go/UnsupportedAppUsage.
+ *
+ * {@hide}
+ */
+@Retention(CLASS)
+@Target({CONSTRUCTOR, METHOD, FIELD, TYPE})
+@Repeatable(UnsupportedAppUsage.Container.class)
+public @interface UnsupportedAppUsage {
+
+    /**
+     * Associates a bug tracking the work to add a public alternative to this API. Optional.
+     *
+     * @return ID of the associated tracking bug
+     */
+    long trackingBug() default 0;
+
+    /**
+     * Indicates that usage of this API is limited to apps based on their target SDK version.
+     *
+     * <p>Access to the API is allowed if the targetSdkVersion in the apps manifest is no greater
+     * than this value. Access checks are performed at runtime.
+     *
+     * <p>This is used to give app developers a grace period to migrate off a non-SDK interface.
+     * When making Android version N, existing APIs can have a maxTargetSdk of N-1 added to them.
+     * Developers must then migrate off the API when their app is updated in future, but it will
+     * continue working in the meantime.
+     *
+     * <p>Possible values are:
+     * <ul>
+     *     <li>
+     *         {@link android.os.Build.VERSION_CODES#O} or {@link android.os.Build.VERSION_CODES#P},
+     *         to limit access to apps targeting these SDKs (or earlier).
+     *     </li>
+     *     <li>
+     *         absent (default value) - All apps can access this API, but doing so may result in
+     *         warnings in the log, UI warnings (on developer builds) and/or strictmode violations.
+     *         The API is likely to be further restricted in future.
+     *     </li>
+     *
+     * </ul>
+     *
+     * Note, if this is set to {@link android.os.Build.VERSION_CODES#O}, apps targeting O
+     * maintenance releases will also be allowed to use the API, and similarly for any future
+     * maintenance releases of P.
+     *
+     * @return The maximum value for an apps targetSdkVersion in order to access this API.
+     */
+    int maxTargetSdk() default Integer.MAX_VALUE;
+
+    /**
+     * For debug use only. The expected dex signature to be generated for this API, used to verify
+     * parts of the build process.
+     *
+     * @return A dex API signature.
+     */
+    String expectedSignature() default "";
+
+    /**
+     * The signature of an implicit (not present in the source) member that forms part of the
+     * hiddenapi.
+     *
+     * <p>Allows access to non-SDK API elements that are not represented in the input source to be
+     * managed.
+     *
+     * <p>This must only be used when applying the annotation to a type, using it in any other
+     * situation is an error.
+     *
+     * @return A dex API signature.
+     */
+    String implicitMember() default "";
+
+    /**
+     * Container for {@link UnsupportedAppUsage} that allows it to be applied repeatedly to types.
+     */
+    @Retention(CLASS)
+    @Target(TYPE)
+    @interface Container {
+        UnsupportedAppUsage[] value();
+    }
+}
diff --git a/android/annotation/UserIdInt.java b/android/annotation/UserIdInt.java
new file mode 100644
index 0000000..7b9ce25
--- /dev/null
+++ b/android/annotation/UserIdInt.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 user ID. This is
+ * <em>not</em> the same as a UID.
+ *
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface UserIdInt {
+}
diff --git a/android/annotation/Widget.java b/android/annotation/Widget.java
new file mode 100644
index 0000000..6756cd7
--- /dev/null
+++ b/android/annotation/Widget.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.Target;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Indicates a class is a widget usable by application developers to create UI.
+ * <p>
+ * This must be used in cases where:
+ * <ul>
+ * <li>The widget is not in the package <code>android.widget</code></li>
+ * <li>The widget extends <code>android.view.ViewGroup</code></li>
+ * </ul>
+ * @hide
+ */
+@Target({ ElementType.TYPE })
+@Retention(RetentionPolicy.SOURCE)
+public @interface Widget {
+}
diff --git a/android/annotation/WorkerThread.java b/android/annotation/WorkerThread.java
new file mode 100644
index 0000000..8c2a4d3
--- /dev/null
+++ b/android/annotation/WorkerThread.java
@@ -0,0 +1,48 @@
+/*
+ * 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 a worker thread.
+ * If the annotated element is a class, then all methods in the class should be
+ * called on a worker thread.
+ * <p>
+ * Example:
+ *
+ * <pre>
+ * <code>
+ *  &#64;WorkerThread
+ *  protected abstract FilterResults performFiltering(CharSequence constraint);
+ * </code>
+ * </pre>
+ *
+ * @memberDoc This method may take several seconds to complete, so it should
+ *            only be called from a worker thread.
+ * @hide
+ */
+@Retention(SOURCE)
+@Target({METHOD,CONSTRUCTOR,TYPE,PARAMETER})
+public @interface WorkerThread {
+}
diff --git a/android/annotation/XmlRes.java b/android/annotation/XmlRes.java
new file mode 100644
index 0000000..5fb8a4a
--- /dev/null
+++ b/android/annotation/XmlRes.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 XML resource reference.
+ *
+ * {@hide}
+ */
+@Documented
+@Retention(SOURCE)
+@Target({METHOD, PARAMETER, FIELD})
+public @interface XmlRes {
+}
diff --git a/android/app/ActionBar.java b/android/app/ActionBar.java
new file mode 100644
index 0000000..e573279
--- /dev/null
+++ b/android/app/ActionBar.java
@@ -0,0 +1,1436 @@
+/*
+ * 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.app;
+
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.LayoutRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.ActionMode;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewGroup;
+import android.view.ViewHierarchyEncoder;
+import android.view.Window;
+import android.view.inspector.InspectableProperty;
+import android.widget.SpinnerAdapter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * A primary toolbar within the activity that may display the activity title, application-level
+ * navigation affordances, and other interactive items.
+ *
+ * <p>Beginning with Android 3.0 (API level 11), the action bar appears at the top of an
+ * activity's window when the activity uses the system's {@link
+ * android.R.style#Theme_Holo Holo} theme (or one of its descendant themes), which is the default.
+ * You may otherwise add the action bar by calling {@link
+ * android.view.Window#requestFeature requestFeature(FEATURE_ACTION_BAR)} or by declaring it in a
+ * custom theme with the {@link android.R.styleable#Theme_windowActionBar windowActionBar} property.
+ * </p>
+ *
+ * <p>Beginning with Android L (API level 21), the action bar may be represented by any
+ * Toolbar widget within the application layout. The application may signal to the Activity
+ * which Toolbar should be treated as the Activity's action bar. Activities that use this
+ * feature should use one of the supplied <code>.NoActionBar</code> themes, set the
+ * {@link android.R.styleable#Theme_windowActionBar windowActionBar} attribute to <code>false</code>
+ * or otherwise not request the window feature.</p>
+ *
+ * <p>By adjusting the window features requested by the theme and the layouts used for
+ * an Activity's content view, an app can use the standard system action bar on older platform
+ * releases and the newer inline toolbars on newer platform releases. The <code>ActionBar</code>
+ * object obtained from the Activity can be used to control either configuration transparently.</p>
+ *
+ * <p>When using the Holo themes the action bar shows the application icon on
+ * the left, followed by the activity title. If your activity has an options menu, you can make
+ * select items accessible directly from the action bar as "action items". You can also
+ * modify various characteristics of the action bar or remove it completely.</p>
+ *
+ * <p>When using the Material themes (default in API 21 or newer) the navigation button
+ * (formerly "Home") takes over the space previously occupied by the application icon.
+ * Apps wishing to express a stronger branding should use their brand colors heavily
+ * in the action bar and other application chrome or use a {@link #setLogo(int) logo}
+ * in place of their standard title text.</p>
+ *
+ * <p>From your activity, you can retrieve an instance of {@link ActionBar} by calling {@link
+ * android.app.Activity#getActionBar getActionBar()}.</p>
+ *
+ * <p>In some cases, the action bar may be overlayed by another bar that enables contextual actions,
+ * using an {@link android.view.ActionMode}. For example, when the user selects one or more items in
+ * your activity, you can enable an action mode that offers actions specific to the selected
+ * items, with a UI that temporarily replaces the action bar. Although the UI may occupy the
+ * same space, the {@link android.view.ActionMode} APIs are distinct and independent from those for
+ * {@link ActionBar}.</p>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For information about how to use the action bar, including how to add action items, navigation
+ * modes and more, read the <a href="{@docRoot}guide/topics/ui/actionbar.html">Action
+ * Bar</a> developer guide.</p>
+ * </div>
+ */
+public abstract class ActionBar {
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "NAVIGATION_MODE_" }, value = {
+            NAVIGATION_MODE_STANDARD,
+            NAVIGATION_MODE_LIST,
+            NAVIGATION_MODE_TABS
+    })
+    public @interface NavigationMode {}
+
+    /**
+     * Standard navigation mode. Consists of either a logo or icon
+     * and title text with an optional subtitle. Clicking any of these elements
+     * will dispatch onOptionsItemSelected to the host Activity with
+     * a MenuItem with item ID android.R.id.home.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public static final int NAVIGATION_MODE_STANDARD = 0;
+    
+    /**
+     * List navigation mode. Instead of static title text this mode
+     * presents a list menu for navigation within the activity.
+     * e.g. this might be presented to the user as a dropdown list.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public static final int NAVIGATION_MODE_LIST = 1;
+    
+    /**
+     * Tab navigation mode. Instead of static title text this mode
+     * presents a series of tabs for navigation within the activity.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public static final int NAVIGATION_MODE_TABS = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "DISPLAY_" }, value = {
+            DISPLAY_USE_LOGO,
+            DISPLAY_SHOW_HOME,
+            DISPLAY_HOME_AS_UP,
+            DISPLAY_SHOW_TITLE,
+            DISPLAY_SHOW_CUSTOM,
+            DISPLAY_TITLE_MULTIPLE_LINES
+    })
+    public @interface DisplayOptions {}
+
+    /**
+     * Use logo instead of icon if available. This flag will cause appropriate
+     * navigation modes to use a wider logo in place of the standard icon.
+     *
+     * @see #setDisplayOptions(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public static final int DISPLAY_USE_LOGO = 0x1;
+    
+    /**
+     * Show 'home' elements in this action bar, leaving more space for other
+     * navigation elements. This includes logo and icon.
+     *
+     * @see #setDisplayOptions(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public static final int DISPLAY_SHOW_HOME = 0x2;
+
+    /**
+     * Display the 'home' element such that it appears as an 'up' affordance.
+     * e.g. show an arrow to the left indicating the action that will be taken.
+     *
+     * Set this flag if selecting the 'home' button in the action bar to return
+     * up by a single level in your UI rather than back to the top level or front page.
+     *
+     * <p>Setting this option will implicitly enable interaction with the home/up
+     * button. See {@link #setHomeButtonEnabled(boolean)}.
+     *
+     * @see #setDisplayOptions(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public static final int DISPLAY_HOME_AS_UP = 0x4;
+
+    /**
+     * Show the activity title and subtitle, if present.
+     *
+     * @see #setTitle(CharSequence)
+     * @see #setTitle(int)
+     * @see #setSubtitle(CharSequence)
+     * @see #setSubtitle(int)
+     * @see #setDisplayOptions(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public static final int DISPLAY_SHOW_TITLE = 0x8;
+
+    /**
+     * Show the custom view if one has been set.
+     * @see #setCustomView(View)
+     * @see #setDisplayOptions(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public static final int DISPLAY_SHOW_CUSTOM = 0x10;
+
+    /**
+     * Allow the title to wrap onto multiple lines if space is available
+     * @hide pending API approval
+     */
+    @UnsupportedAppUsage
+    public static final int DISPLAY_TITLE_MULTIPLE_LINES = 0x20;
+
+    /**
+     * Set the action bar into custom navigation mode, supplying a view
+     * for custom navigation.
+     *
+     * Custom navigation views appear between the application icon and
+     * any action buttons and may use any space available there. Common
+     * use cases for custom navigation views might include an auto-suggesting
+     * address bar for a browser or other navigation mechanisms that do not
+     * translate well to provided navigation modes.
+     *
+     * @param view Custom navigation view to place in the ActionBar.
+     */
+    public abstract void setCustomView(View view);
+
+    /**
+     * Set the action bar into custom navigation mode, supplying a view
+     * for custom navigation.
+     * 
+     * <p>Custom navigation views appear between the application icon and
+     * any action buttons and may use any space available there. Common
+     * use cases for custom navigation views might include an auto-suggesting
+     * address bar for a browser or other navigation mechanisms that do not
+     * translate well to provided navigation modes.</p>
+     *
+     * <p>The display option {@link #DISPLAY_SHOW_CUSTOM} must be set for
+     * the custom view to be displayed.</p>
+     * 
+     * @param view Custom navigation view to place in the ActionBar.
+     * @param layoutParams How this custom view should layout in the bar.
+     *
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setCustomView(View view, LayoutParams layoutParams);
+
+    /**
+     * Set the action bar into custom navigation mode, supplying a view
+     * for custom navigation.
+     *
+     * <p>Custom navigation views appear between the application icon and
+     * any action buttons and may use any space available there. Common
+     * use cases for custom navigation views might include an auto-suggesting
+     * address bar for a browser or other navigation mechanisms that do not
+     * translate well to provided navigation modes.</p>
+     *
+     * <p>The display option {@link #DISPLAY_SHOW_CUSTOM} must be set for
+     * the custom view to be displayed.</p>
+     *
+     * @param resId Resource ID of a layout to inflate into the ActionBar.
+     *
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setCustomView(@LayoutRes int resId);
+
+    /**
+     * Set the icon to display in the 'home' section of the action bar.
+     * The action bar will use an icon specified by its style or the
+     * activity icon by default.
+     *
+     * Whether the home section shows an icon or logo is controlled
+     * by the display option {@link #DISPLAY_USE_LOGO}.
+     *
+     * @param resId Resource ID of a drawable to show as an icon.
+     *
+     * @see #setDisplayUseLogoEnabled(boolean)
+     * @see #setDisplayShowHomeEnabled(boolean)
+     */
+    public abstract void setIcon(@DrawableRes int resId);
+
+    /**
+     * Set the icon to display in the 'home' section of the action bar.
+     * The action bar will use an icon specified by its style or the
+     * activity icon by default.
+     *
+     * Whether the home section shows an icon or logo is controlled
+     * by the display option {@link #DISPLAY_USE_LOGO}.
+     *
+     * @param icon Drawable to show as an icon.
+     *
+     * @see #setDisplayUseLogoEnabled(boolean)
+     * @see #setDisplayShowHomeEnabled(boolean)
+     */
+    public abstract void setIcon(Drawable icon);
+
+    /**
+     * Set the logo to display in the 'home' section of the action bar.
+     * The action bar will use a logo specified by its style or the
+     * activity logo by default.
+     *
+     * Whether the home section shows an icon or logo is controlled
+     * by the display option {@link #DISPLAY_USE_LOGO}.
+     *
+     * @param resId Resource ID of a drawable to show as a logo.
+     *
+     * @see #setDisplayUseLogoEnabled(boolean)
+     * @see #setDisplayShowHomeEnabled(boolean)
+     */
+    public abstract void setLogo(@DrawableRes int resId);
+
+    /**
+     * Set the logo to display in the 'home' section of the action bar.
+     * The action bar will use a logo specified by its style or the
+     * activity logo by default.
+     *
+     * Whether the home section shows an icon or logo is controlled
+     * by the display option {@link #DISPLAY_USE_LOGO}.
+     *
+     * @param logo Drawable to show as a logo.
+     *
+     * @see #setDisplayUseLogoEnabled(boolean)
+     * @see #setDisplayShowHomeEnabled(boolean)
+     */
+    public abstract void setLogo(Drawable logo);
+
+    /**
+     * Set the adapter and navigation callback for list navigation mode.
+     *
+     * The supplied adapter will provide views for the expanded list as well as
+     * the currently selected item. (These may be displayed differently.)
+     *
+     * The supplied OnNavigationListener will alert the application when the user
+     * changes the current list selection.
+     *
+     * @param adapter An adapter that will provide views both to display
+     *                the current navigation selection and populate views
+     *                within the dropdown navigation menu.
+     * @param callback An OnNavigationListener that will receive events when the user
+     *                 selects a navigation item.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void setListNavigationCallbacks(SpinnerAdapter adapter,
+            OnNavigationListener callback);
+
+    /**
+     * Set the selected navigation item in list or tabbed navigation modes.
+     *
+     * @param position Position of the item to select.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void setSelectedNavigationItem(int position);
+
+    /**
+     * Get the position of the selected navigation item in list or tabbed navigation modes.
+     *
+     * @return Position of the selected item.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract int getSelectedNavigationIndex();
+
+    /**
+     * Get the number of navigation items present in the current navigation mode.
+     *
+     * @return Number of navigation items.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract int getNavigationItemCount();
+
+    /**
+     * Set the action bar's title. This will only be displayed if
+     * {@link #DISPLAY_SHOW_TITLE} is set.
+     *
+     * @param title Title to set
+     *
+     * @see #setTitle(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setTitle(CharSequence title);
+
+    /**
+     * Set the action bar's title. This will only be displayed if
+     * {@link #DISPLAY_SHOW_TITLE} is set.
+     *
+     * @param resId Resource ID of title string to set
+     *
+     * @see #setTitle(CharSequence)
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setTitle(@StringRes int resId);
+
+    /**
+     * Set the action bar's subtitle. This will only be displayed if
+     * {@link #DISPLAY_SHOW_TITLE} is set. Set to null to disable the
+     * subtitle entirely.
+     *
+     * @param subtitle Subtitle to set
+     *
+     * @see #setSubtitle(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setSubtitle(CharSequence subtitle);
+
+    /**
+     * Set the action bar's subtitle. This will only be displayed if
+     * {@link #DISPLAY_SHOW_TITLE} is set.
+     *
+     * @param resId Resource ID of subtitle string to set
+     *
+     * @see #setSubtitle(CharSequence)
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setSubtitle(@StringRes int resId);
+
+    /**
+     * Set display options. This changes all display option bits at once. To change
+     * a limited subset of display options, see {@link #setDisplayOptions(int, int)}.
+     * 
+     * @param options A combination of the bits defined by the DISPLAY_ constants
+     *                defined in ActionBar.
+     */
+    public abstract void setDisplayOptions(@DisplayOptions int options);
+    
+    /**
+     * Set selected display options. Only the options specified by mask will be changed.
+     * To change all display option bits at once, see {@link #setDisplayOptions(int)}.
+     * 
+     * <p>Example: setDisplayOptions(0, DISPLAY_SHOW_HOME) will disable the
+     * {@link #DISPLAY_SHOW_HOME} option.
+     * setDisplayOptions(DISPLAY_SHOW_HOME, DISPLAY_SHOW_HOME | DISPLAY_USE_LOGO)
+     * will enable {@link #DISPLAY_SHOW_HOME} and disable {@link #DISPLAY_USE_LOGO}.
+     * 
+     * @param options A combination of the bits defined by the DISPLAY_ constants
+     *                defined in ActionBar.
+     * @param mask A bit mask declaring which display options should be changed.
+     */
+    public abstract void setDisplayOptions(@DisplayOptions int options, @DisplayOptions int mask);
+
+    /**
+     * Set whether to display the activity logo rather than the activity icon.
+     * A logo is often a wider, more detailed image.
+     *
+     * <p>To set several display options at once, see the setDisplayOptions methods.
+     *
+     * @param useLogo true to use the activity logo, false to use the activity icon.
+     *
+     * @see #setDisplayOptions(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setDisplayUseLogoEnabled(boolean useLogo);
+
+    /**
+     * Set whether to include the application home affordance in the action bar.
+     * Home is presented as either an activity icon or logo.
+     *
+     * <p>To set several display options at once, see the setDisplayOptions methods.
+     *
+     * @param showHome true to show home, false otherwise.
+     *
+     * @see #setDisplayOptions(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setDisplayShowHomeEnabled(boolean showHome);
+
+    /**
+     * Set whether home should be displayed as an "up" affordance.
+     * Set this to true if selecting "home" returns up by a single level in your UI
+     * rather than back to the top level or front page.
+     *
+     * <p>To set several display options at once, see the setDisplayOptions methods.
+     *
+     * @param showHomeAsUp true to show the user that selecting home will return one
+     *                     level up rather than to the top level of the app.
+     *
+     * @see #setDisplayOptions(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setDisplayHomeAsUpEnabled(boolean showHomeAsUp);
+
+    /**
+     * Set whether an activity title/subtitle should be displayed.
+     *
+     * <p>To set several display options at once, see the setDisplayOptions methods.
+     *
+     * @param showTitle true to display a title/subtitle if present.
+     *
+     * @see #setDisplayOptions(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setDisplayShowTitleEnabled(boolean showTitle);
+
+    /**
+     * Set whether a custom view should be displayed, if set.
+     *
+     * <p>To set several display options at once, see the setDisplayOptions methods.
+     *
+     * @param showCustom true if the currently set custom view should be displayed, false otherwise.
+     *
+     * @see #setDisplayOptions(int)
+     * @see #setDisplayOptions(int, int)
+     */
+    public abstract void setDisplayShowCustomEnabled(boolean showCustom);
+
+    /**
+     * Set the ActionBar's background. This will be used for the primary
+     * action bar.
+     * 
+     * @param d Background drawable
+     * @see #setStackedBackgroundDrawable(Drawable)
+     * @see #setSplitBackgroundDrawable(Drawable)
+     */
+    public abstract void setBackgroundDrawable(@Nullable Drawable d);
+
+    /**
+     * Set the ActionBar's stacked background. This will appear
+     * in the second row/stacked bar on some devices and configurations.
+     *
+     * @param d Background drawable for the stacked row
+     */
+    public void setStackedBackgroundDrawable(Drawable d) { }
+
+    /**
+     * Set the ActionBar's split background. This will appear in
+     * the split action bar containing menu-provided action buttons
+     * on some devices and configurations.
+     * <p>You can enable split action bar with {@link android.R.attr#uiOptions}
+     *
+     * @param d Background drawable for the split bar
+     */
+    public void setSplitBackgroundDrawable(Drawable d) { }
+
+    /**
+     * @return The current custom view.
+     */
+    public abstract View getCustomView();
+
+    /**
+     * Returns the current ActionBar title in standard mode.
+     * Returns null if {@link #getNavigationMode()} would not return
+     * {@link #NAVIGATION_MODE_STANDARD}. 
+     *
+     * @return The current ActionBar title or null.
+     */
+    public abstract CharSequence getTitle();
+    
+    /**
+     * Returns the current ActionBar subtitle in standard mode.
+     * Returns null if {@link #getNavigationMode()} would not return
+     * {@link #NAVIGATION_MODE_STANDARD}. 
+     *
+     * @return The current ActionBar subtitle or null.
+     */
+    public abstract CharSequence getSubtitle();
+    
+    /**
+     * Returns the current navigation mode. The result will be one of:
+     * <ul>
+     * <li>{@link #NAVIGATION_MODE_STANDARD}</li>
+     * <li>{@link #NAVIGATION_MODE_LIST}</li>
+     * <li>{@link #NAVIGATION_MODE_TABS}</li>
+     * </ul>
+     *
+     * @return The current navigation mode.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    @NavigationMode
+    public abstract int getNavigationMode();
+
+    /**
+     * Set the current navigation mode.
+     *
+     * @param mode The new mode to set.
+     * @see #NAVIGATION_MODE_STANDARD
+     * @see #NAVIGATION_MODE_LIST
+     * @see #NAVIGATION_MODE_TABS
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void setNavigationMode(@NavigationMode int mode);
+
+    /**
+     * @return The current set of display options. 
+     */
+    public abstract int getDisplayOptions();
+
+    /**
+     * Create and return a new {@link Tab}.
+     * This tab will not be included in the action bar until it is added.
+     *
+     * <p>Very often tabs will be used to switch between {@link Fragment}
+     * objects.  Here is a typical implementation of such tabs:</p>
+     *
+     * {@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentTabs.java
+     *      complete}
+     *
+     * @return A new Tab
+     *
+     * @see #addTab(Tab)
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract Tab newTab();
+
+    /**
+     * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list.
+     * If this is the first tab to be added it will become the selected tab.
+     *
+     * @param tab Tab to add
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void addTab(Tab tab);
+
+    /**
+     * Add a tab for use in tabbed navigation mode. The tab will be added at the end of the list.
+     *
+     * @param tab Tab to add
+     * @param setSelected True if the added tab should become the selected tab.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void addTab(Tab tab, boolean setSelected);
+
+    /**
+     * Add a tab for use in tabbed navigation mode. The tab will be inserted at
+     * <code>position</code>. If this is the first tab to be added it will become
+     * the selected tab.
+     *
+     * @param tab The tab to add
+     * @param position The new position of the tab
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void addTab(Tab tab, int position);
+
+    /**
+     * Add a tab for use in tabbed navigation mode. The tab will be insterted at
+     * <code>position</code>.
+     *
+     * @param tab The tab to add
+     * @param position The new position of the tab
+     * @param setSelected True if the added tab should become the selected tab.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void addTab(Tab tab, int position, boolean setSelected);
+
+    /**
+     * Remove a tab from the action bar. If the removed tab was selected it will be deselected
+     * and another tab will be selected if present.
+     *
+     * @param tab The tab to remove
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void removeTab(Tab tab);
+
+    /**
+     * Remove a tab from the action bar. If the removed tab was selected it will be deselected
+     * and another tab will be selected if present.
+     *
+     * @param position Position of the tab to remove
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void removeTabAt(int position);
+
+    /**
+     * Remove all tabs from the action bar and deselect the current tab.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void removeAllTabs();
+
+    /**
+     * Select the specified tab. If it is not a child of this action bar it will be added.
+     *
+     * <p>Note: If you want to select by index, use {@link #setSelectedNavigationItem(int)}.</p>
+     *
+     * @param tab Tab to select
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract void selectTab(Tab tab);
+
+    /**
+     * Returns the currently selected tab if in tabbed navigation mode and there is at least
+     * one tab present.
+     *
+     * @return The currently selected tab or null
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract Tab getSelectedTab();
+
+    /**
+     * Returns the tab at the specified index.
+     *
+     * @param index Index value in the range 0-get
+     * @return
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract Tab getTabAt(int index);
+
+    /**
+     * Returns the number of tabs currently registered with the action bar.
+     * @return Tab count
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public abstract int getTabCount();
+
+    /**
+     * Retrieve the current height of the ActionBar.
+     *
+     * @return The ActionBar's height
+     */
+    public abstract int getHeight();
+
+    /**
+     * Show the ActionBar if it is not currently showing.
+     * If the window hosting the ActionBar does not have the feature
+     * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application
+     * content to fit the new space available.
+     *
+     * <p>If you are hiding the ActionBar through
+     * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN},
+     * you should not call this function directly.
+     */
+    public abstract void show();
+
+    /**
+     * Hide the ActionBar if it is currently showing.
+     * If the window hosting the ActionBar does not have the feature
+     * {@link Window#FEATURE_ACTION_BAR_OVERLAY} it will resize application
+     * content to fit the new space available.
+     *
+     * <p>Instead of calling this function directly, you can also cause an
+     * ActionBar using the overlay feature to hide through
+     * {@link View#SYSTEM_UI_FLAG_FULLSCREEN View.SYSTEM_UI_FLAG_FULLSCREEN}.
+     * Hiding the ActionBar through this system UI flag allows you to more
+     * seamlessly hide it in conjunction with other screen decorations.
+     */
+    public abstract void hide();
+
+    /**
+     * @return <code>true</code> if the ActionBar is showing, <code>false</code> otherwise.
+     */
+    public abstract boolean isShowing();
+
+    /**
+     * Add a listener that will respond to menu visibility change events.
+     *
+     * @param listener The new listener to add
+     */
+    public abstract void addOnMenuVisibilityListener(OnMenuVisibilityListener listener);
+
+    /**
+     * Remove a menu visibility listener. This listener will no longer receive menu
+     * visibility change events.
+     *
+     * @param listener A listener to remove that was previously added
+     */
+    public abstract void removeOnMenuVisibilityListener(OnMenuVisibilityListener listener);
+
+    /**
+     * Enable or disable the "home" button in the corner of the action bar. (Note that this
+     * is the application home/up affordance on the action bar, not the systemwide home
+     * button.)
+     *
+     * <p>This defaults to true for packages targeting &lt; API 14. For packages targeting
+     * API 14 or greater, the application should call this method to enable interaction
+     * with the home/up affordance.
+     *
+     * <p>Setting the {@link #DISPLAY_HOME_AS_UP} display option will automatically enable
+     * the home button.
+     *
+     * @param enabled true to enable the home button, false to disable the home button.
+     */
+    public void setHomeButtonEnabled(boolean enabled) { }
+
+    /**
+     * Returns a {@link Context} with an appropriate theme for creating views that
+     * will appear in the action bar. If you are inflating or instantiating custom views
+     * that will appear in an action bar, you should use the Context returned by this method.
+     * (This includes adapters used for list navigation mode.)
+     * This will ensure that views contrast properly against the action bar.
+     *
+     * @return A themed Context for creating views
+     */
+    public Context getThemedContext() { return null; }
+
+    /**
+     * Returns true if the Title field has been truncated during layout for lack
+     * of available space.
+     *
+     * @return true if the Title field has been truncated
+     * @hide pending API approval
+     */
+    public boolean isTitleTruncated() { return false; }
+
+    /**
+     * Set an alternate drawable to display next to the icon/logo/title
+     * when {@link #DISPLAY_HOME_AS_UP} is enabled. This can be useful if you are using
+     * this mode to display an alternate selection for up navigation, such as a sliding drawer.
+     *
+     * <p>If you pass <code>null</code> to this method, the default drawable from the theme
+     * will be used.</p>
+     *
+     * <p>If you implement alternate or intermediate behavior around Up, you should also
+     * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()}
+     * to provide a correct description of the action for accessibility support.</p>
+     *
+     * @param indicator A drawable to use for the up indicator, or null to use the theme's default
+     *
+     * @see #setDisplayOptions(int, int)
+     * @see #setDisplayHomeAsUpEnabled(boolean)
+     * @see #setHomeActionContentDescription(int)
+     */
+    public void setHomeAsUpIndicator(Drawable indicator) { }
+
+    /**
+     * Set an alternate drawable to display next to the icon/logo/title
+     * when {@link #DISPLAY_HOME_AS_UP} is enabled. This can be useful if you are using
+     * this mode to display an alternate selection for up navigation, such as a sliding drawer.
+     *
+     * <p>If you pass <code>0</code> to this method, the default drawable from the theme
+     * will be used.</p>
+     *
+     * <p>If you implement alternate or intermediate behavior around Up, you should also
+     * call {@link #setHomeActionContentDescription(int) setHomeActionContentDescription()}
+     * to provide a correct description of the action for accessibility support.</p>
+     *
+     * @param resId Resource ID of a drawable to use for the up indicator, or null
+     *              to use the theme's default
+     *
+     * @see #setDisplayOptions(int, int)
+     * @see #setDisplayHomeAsUpEnabled(boolean)
+     * @see #setHomeActionContentDescription(int)
+     */
+    public void setHomeAsUpIndicator(@DrawableRes int resId) { }
+
+    /**
+     * Set an alternate description for the Home/Up action, when enabled.
+     *
+     * <p>This description is commonly used for accessibility/screen readers when
+     * the Home action is enabled. (See {@link #setDisplayHomeAsUpEnabled(boolean)}.)
+     * Examples of this are, "Navigate Home" or "Navigate Up" depending on the
+     * {@link #DISPLAY_HOME_AS_UP} display option. If you have changed the home-as-up
+     * indicator using {@link #setHomeAsUpIndicator(int)} to indicate more specific
+     * functionality such as a sliding drawer, you should also set this to accurately
+     * describe the action.</p>
+     *
+     * <p>Setting this to <code>null</code> will use the system default description.</p>
+     *
+     * @param description New description for the Home action when enabled
+     * @see #setHomeAsUpIndicator(int)
+     * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
+     */
+    public void setHomeActionContentDescription(CharSequence description) { }
+
+    /**
+     * Set an alternate description for the Home/Up action, when enabled.
+     *
+     * <p>This description is commonly used for accessibility/screen readers when
+     * the Home action is enabled. (See {@link #setDisplayHomeAsUpEnabled(boolean)}.)
+     * Examples of this are, "Navigate Home" or "Navigate Up" depending on the
+     * {@link #DISPLAY_HOME_AS_UP} display option. If you have changed the home-as-up
+     * indicator using {@link #setHomeAsUpIndicator(int)} to indicate more specific
+     * functionality such as a sliding drawer, you should also set this to accurately
+     * describe the action.</p>
+     *
+     * <p>Setting this to <code>0</code> will use the system default description.</p>
+     *
+     * @param resId Resource ID of a string to use as the new description
+     *              for the Home action when enabled
+     * @see #setHomeAsUpIndicator(int)
+     * @see #setHomeAsUpIndicator(android.graphics.drawable.Drawable)
+     */
+    public void setHomeActionContentDescription(@StringRes int resId) { }
+
+    /**
+     * Enable hiding the action bar on content scroll.
+     *
+     * <p>If enabled, the action bar will scroll out of sight along with a
+     * {@link View#setNestedScrollingEnabled(boolean) nested scrolling child} view's content.
+     * The action bar must be in {@link Window#FEATURE_ACTION_BAR_OVERLAY overlay mode}
+     * to enable hiding on content scroll.</p>
+     *
+     * <p>When partially scrolled off screen the action bar is considered
+     * {@link #hide() hidden}. A call to {@link #show() show} will cause it to return to full view.
+     * </p>
+     * @param hideOnContentScroll true to enable hiding on content scroll.
+     */
+    public void setHideOnContentScrollEnabled(boolean hideOnContentScroll) {
+        if (hideOnContentScroll) {
+            throw new UnsupportedOperationException("Hide on content scroll is not supported in " +
+                    "this action bar configuration.");
+        }
+    }
+
+    /**
+     * Return whether the action bar is configured to scroll out of sight along with
+     * a {@link View#setNestedScrollingEnabled(boolean) nested scrolling child}.
+     *
+     * @return true if hide-on-content-scroll is enabled
+     * @see #setHideOnContentScrollEnabled(boolean)
+     */
+    public boolean isHideOnContentScrollEnabled() {
+        return false;
+    }
+
+    /**
+     * Return the current vertical offset of the action bar.
+     *
+     * <p>The action bar's current hide offset is the distance that the action bar is currently
+     * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+     * current measured {@link #getHeight() height} (fully invisible).</p>
+     *
+     * @return The action bar's offset toward its fully hidden state in pixels
+     */
+    public int getHideOffset() {
+        return 0;
+    }
+
+    /**
+     * Set the current hide offset of the action bar.
+     *
+     * <p>The action bar's current hide offset is the distance that the action bar is currently
+     * scrolled offscreen in pixels. The valid range is 0 (fully visible) to the action bar's
+     * current measured {@link #getHeight() height} (fully invisible).</p>
+     *
+     * @param offset The action bar's offset toward its fully hidden state in pixels.
+     */
+    public void setHideOffset(int offset) {
+        if (offset != 0) {
+            throw new UnsupportedOperationException("Setting an explicit action bar hide offset " +
+                    "is not supported in this action bar configuration.");
+        }
+    }
+
+    /**
+     * Set the Z-axis elevation of the action bar in pixels.
+     *
+     * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher
+     * values are closer to the user.</p>
+     *
+     * @param elevation Elevation value in pixels
+     */
+    public void setElevation(float elevation) {
+        if (elevation != 0) {
+            throw new UnsupportedOperationException("Setting a non-zero elevation is " +
+                    "not supported in this action bar configuration.");
+        }
+    }
+
+    /**
+     * Get the Z-axis elevation of the action bar in pixels.
+     *
+     * <p>The action bar's elevation is the distance it is placed from its parent surface. Higher
+     * values are closer to the user.</p>
+     *
+     * @return Elevation value in pixels
+     */
+    public float getElevation() {
+        return 0;
+    }
+
+    /** @hide */
+    public void setDefaultDisplayHomeAsUpEnabled(boolean enabled) {
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public void setShowHideAnimationEnabled(boolean enabled) {
+    }
+
+    /** @hide */
+    public void onConfigurationChanged(Configuration config) {
+    }
+
+    /** @hide */
+    public void dispatchMenuVisibilityChanged(boolean visible) {
+    }
+
+    /** @hide */
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        return null;
+    }
+
+    /** @hide */
+    public boolean openOptionsMenu() {
+        return false;
+    }
+
+    /** @hide */
+    public boolean closeOptionsMenu() {
+        return false;
+    }
+
+    /** @hide */
+    public boolean invalidateOptionsMenu() {
+        return false;
+    }
+
+    /** @hide */
+    public boolean onMenuKeyEvent(KeyEvent event) {
+        return false;
+    }
+
+    /** @hide */
+    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public boolean collapseActionView() {
+        return false;
+    }
+
+    /** @hide */
+    public void setWindowTitle(CharSequence title) {
+    }
+
+    /** @hide */
+    public void onDestroy() {
+    }
+
+    /**
+     * Listener interface for ActionBar navigation events.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public interface OnNavigationListener {
+        /**
+         * This method is called whenever a navigation item in your action bar
+         * is selected.
+         *    
+         * @param itemPosition Position of the item clicked.
+         * @param itemId ID of the item clicked.
+         * @return True if the event was handled, false otherwise.
+         */
+        public boolean onNavigationItemSelected(int itemPosition, long itemId);
+    }
+
+    /**
+     * Listener for receiving events when action bar menus are shown or hidden.
+     */
+    public interface OnMenuVisibilityListener {
+        /**
+         * Called when an action bar menu is shown or hidden. Applications may want to use
+         * this to tune auto-hiding behavior for the action bar or pause/resume video playback,
+         * gameplay, or other activity within the main content area.
+         *
+         * @param isVisible True if an action bar menu is now visible, false if no action bar
+         *                  menus are visible.
+         */
+        public void onMenuVisibilityChanged(boolean isVisible);
+    }
+
+    /**
+     * A tab in the action bar.
+     *
+     * <p>Tabs manage the hiding and showing of {@link Fragment}s.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public static abstract class Tab {
+        /**
+         * An invalid position for a tab.
+         *
+         * @see #getPosition()
+         */
+        public static final int INVALID_POSITION = -1;
+
+        /**
+         * Return the current position of this tab in the action bar.
+         *
+         * @return Current position, or {@link #INVALID_POSITION} if this tab is not currently in
+         *         the action bar.
+         */
+        public abstract int getPosition();
+
+        /**
+         * Return the icon associated with this tab.
+         *
+         * @return The tab's icon
+         */
+        public abstract Drawable getIcon();
+
+        /**
+         * Return the text of this tab.
+         *
+         * @return The tab's text
+         */
+        public abstract CharSequence getText();
+
+        /**
+         * Set the icon displayed on this tab.
+         *
+         * @param icon The drawable to use as an icon
+         * @return The current instance for call chaining
+         */
+        public abstract Tab setIcon(Drawable icon);
+
+        /**
+         * Set the icon displayed on this tab.
+         *
+         * @param resId Resource ID referring to the drawable to use as an icon
+         * @return The current instance for call chaining
+         */
+        public abstract Tab setIcon(@DrawableRes int resId);
+
+        /**
+         * Set the text displayed on this tab. Text may be truncated if there is not
+         * room to display the entire string.
+         *
+         * @param text The text to display
+         * @return The current instance for call chaining
+         */
+        public abstract Tab setText(CharSequence text);
+
+        /**
+         * Set the text displayed on this tab. Text may be truncated if there is not
+         * room to display the entire string.
+         *
+         * @param resId A resource ID referring to the text that should be displayed
+         * @return The current instance for call chaining
+         */
+        public abstract Tab setText(@StringRes int resId);
+
+        /**
+         * Set a custom view to be used for this tab. This overrides values set by
+         * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+         *
+         * @param view Custom view to be used as a tab.
+         * @return The current instance for call chaining
+         */
+        public abstract Tab setCustomView(View view);
+
+        /**
+         * Set a custom view to be used for this tab. This overrides values set by
+         * {@link #setText(CharSequence)} and {@link #setIcon(Drawable)}.
+         *
+         * @param layoutResId A layout resource to inflate and use as a custom tab view
+         * @return The current instance for call chaining
+         */
+        public abstract Tab setCustomView(@LayoutRes int layoutResId);
+
+        /**
+         * Retrieve a previously set custom view for this tab.
+         *
+         * @return The custom view set by {@link #setCustomView(View)}.
+         */
+        public abstract View getCustomView();
+
+        /**
+         * Give this Tab an arbitrary object to hold for later use.
+         *
+         * @param obj Object to store
+         * @return The current instance for call chaining
+         */
+        public abstract Tab setTag(Object obj);
+
+        /**
+         * @return This Tab's tag object.
+         */
+        public abstract Object getTag();
+
+        /**
+         * Set the {@link TabListener} that will handle switching to and from this tab.
+         * All tabs must have a TabListener set before being added to the ActionBar.
+         *
+         * @param listener Listener to handle tab selection events
+         * @return The current instance for call chaining
+         */
+        public abstract Tab setTabListener(TabListener listener);
+
+        /**
+         * Select this tab. Only valid if the tab has been added to the action bar.
+         */
+        public abstract void select();
+
+        /**
+         * Set a description of this tab's content for use in accessibility support.
+         * If no content description is provided the title will be used.
+         *
+         * @param resId A resource ID referring to the description text
+         * @return The current instance for call chaining
+         * @see #setContentDescription(CharSequence)
+         * @see #getContentDescription()
+         */
+        public abstract Tab setContentDescription(@StringRes int resId);
+
+        /**
+         * Set a description of this tab's content for use in accessibility support.
+         * If no content description is provided the title will be used.
+         *
+         * @param contentDesc Description of this tab's content
+         * @return The current instance for call chaining
+         * @see #setContentDescription(int)
+         * @see #getContentDescription()
+         */
+        public abstract Tab setContentDescription(CharSequence contentDesc);
+
+        /**
+         * Gets a brief description of this tab's content for use in accessibility support.
+         *
+         * @return Description of this tab's content
+         * @see #setContentDescription(CharSequence)
+         * @see #setContentDescription(int)
+         */
+        public abstract CharSequence getContentDescription();
+    }
+
+    /**
+     * Callback interface invoked when a tab is focused, unfocused, added, or removed.
+     *
+     * @deprecated Action bar navigation modes are deprecated and not supported by inline
+     * toolbar action bars. Consider using other
+     * <a href="http://developer.android.com/design/patterns/navigation.html">common
+     * navigation patterns</a> instead.
+     */
+    @Deprecated
+    public interface TabListener {
+        /**
+         * Called when a tab enters the selected state.
+         *
+         * @param tab The tab that was selected
+         * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+         *        during a tab switch. The previous tab's unselect and this tab's select will be
+         *        executed in a single transaction. This FragmentTransaction does not support
+         *        being added to the back stack.
+         */
+        public void onTabSelected(Tab tab, FragmentTransaction ft);
+
+        /**
+         * Called when a tab exits the selected state.
+         *
+         * @param tab The tab that was unselected
+         * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+         *        during a tab switch. This tab's unselect and the newly selected tab's select
+         *        will be executed in a single transaction. This FragmentTransaction does not
+         *        support being added to the back stack.
+         */
+        public void onTabUnselected(Tab tab, FragmentTransaction ft);
+
+        /**
+         * Called when a tab that is already selected is chosen again by the user.
+         * Some applications may use this action to return to the top level of a category.
+         *
+         * @param tab The tab that was reselected.
+         * @param ft A {@link FragmentTransaction} for queuing fragment operations to execute
+         *        once this method returns. This FragmentTransaction does not support
+         *        being added to the back stack.
+         */
+        public void onTabReselected(Tab tab, FragmentTransaction ft);
+    }
+
+    /**
+     * Per-child layout information associated with action bar custom views.
+     *
+     * @attr ref android.R.styleable#ActionBar_LayoutParams_layout_gravity
+     */
+    public static class LayoutParams extends ViewGroup.MarginLayoutParams {
+        /**
+         * Gravity for the view associated with these LayoutParams.
+         *
+         * @see android.view.Gravity
+         */
+        @ViewDebug.ExportedProperty(category = "layout", mapping = {
+                @ViewDebug.IntToString(from =  -1,                       to = "NONE"),
+                @ViewDebug.IntToString(from = Gravity.NO_GRAVITY,        to = "NONE"),
+                @ViewDebug.IntToString(from = Gravity.TOP,               to = "TOP"),
+                @ViewDebug.IntToString(from = Gravity.BOTTOM,            to = "BOTTOM"),
+                @ViewDebug.IntToString(from = Gravity.LEFT,              to = "LEFT"),
+                @ViewDebug.IntToString(from = Gravity.RIGHT,             to = "RIGHT"),
+                @ViewDebug.IntToString(from = Gravity.START,             to = "START"),
+                @ViewDebug.IntToString(from = Gravity.END,               to = "END"),
+                @ViewDebug.IntToString(from = Gravity.CENTER_VERTICAL,   to = "CENTER_VERTICAL"),
+                @ViewDebug.IntToString(from = Gravity.FILL_VERTICAL,     to = "FILL_VERTICAL"),
+                @ViewDebug.IntToString(from = Gravity.CENTER_HORIZONTAL, to = "CENTER_HORIZONTAL"),
+                @ViewDebug.IntToString(from = Gravity.FILL_HORIZONTAL,   to = "FILL_HORIZONTAL"),
+                @ViewDebug.IntToString(from = Gravity.CENTER,            to = "CENTER"),
+                @ViewDebug.IntToString(from = Gravity.FILL,              to = "FILL")
+        })
+        @InspectableProperty(
+                name = "layout_gravity",
+                valueType = InspectableProperty.ValueType.GRAVITY)
+        public int gravity = Gravity.NO_GRAVITY;
+
+        public LayoutParams(@NonNull Context c, AttributeSet attrs) {
+            super(c, attrs);
+
+            TypedArray a = c.obtainStyledAttributes(attrs,
+                    com.android.internal.R.styleable.ActionBar_LayoutParams);
+            gravity = a.getInt(
+                    com.android.internal.R.styleable.ActionBar_LayoutParams_layout_gravity,
+                    Gravity.NO_GRAVITY);
+            a.recycle();
+        }
+
+        public LayoutParams(int width, int height) {
+            super(width, height);
+            this.gravity = Gravity.CENTER_VERTICAL | Gravity.START;
+        }
+
+        public LayoutParams(int width, int height, int gravity) {
+            super(width, height);
+
+            this.gravity = gravity;
+        }
+
+        public LayoutParams(int gravity) {
+            this(WRAP_CONTENT, MATCH_PARENT, gravity);
+        }
+
+        public LayoutParams(LayoutParams source) {
+            super(source);
+            this.gravity = source.gravity;
+        }
+
+        public LayoutParams(ViewGroup.LayoutParams source) {
+            super(source);
+        }
+
+        /*
+         * Note for framework developers:
+         *
+         * You might notice that ActionBar.LayoutParams is missing a constructor overload
+         * for MarginLayoutParams. While it may seem like a good idea to add one, at this
+         * point it's dangerous for source compatibility. Upon building against a new
+         * version of the SDK an app can end up statically linking to the new MarginLayoutParams
+         * overload, causing a crash when running on older platform versions with no other changes.
+         */
+
+        /** @hide */
+        @Override
+        protected void encodeProperties(@NonNull ViewHierarchyEncoder encoder) {
+            super.encodeProperties(encoder);
+
+            encoder.addProperty("gravity", gravity);
+        }
+    }
+}
diff --git a/android/app/Activity.java b/android/app/Activity.java
new file mode 100644
index 0000000..dc52c52
--- /dev/null
+++ b/android/app/Activity.java
@@ -0,0 +1,8676 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.os.Process.myUid;
+
+import static java.lang.Character.MIN_VALUE;
+
+import android.annotation.CallSuper;
+import android.annotation.DrawableRes;
+import android.annotation.IdRes;
+import android.annotation.IntDef;
+import android.annotation.LayoutRes;
+import android.annotation.MainThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.StyleRes;
+import android.annotation.SystemApi;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.VoiceInteractor.Request;
+import android.app.admin.DevicePolicyManager;
+import android.app.assist.AssistContent;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.CursorLoader;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.content.SharedPreferences;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.media.session.MediaController;
+import android.net.Uri;
+import android.os.BadParcelableException;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.GraphicsEnvironment;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager.ServiceNotFoundException;
+import android.os.StrictMode;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.text.Selection;
+import android.text.SpannableStringBuilder;
+import android.text.TextUtils;
+import android.text.method.TextKeyListener;
+import android.transition.Scene;
+import android.transition.TransitionManager;
+import android.util.ArrayMap;
+import android.util.AttributeSet;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SuperNotCalledException;
+import android.view.ActionMode;
+import android.view.ContextMenu;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.ContextThemeWrapper;
+import android.view.DragAndDropPermissions;
+import android.view.DragEvent;
+import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
+import android.view.KeyboardShortcutInfo;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.RemoteAnimationDefinition;
+import android.view.SearchEvent;
+import android.view.View;
+import android.view.View.OnCreateContextMenuListener;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
+import android.view.ViewManager;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.ActivityConfigCallback;
+import android.view.Window;
+import android.view.Window.WindowControllerCallback;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillManager.AutofillClient;
+import android.view.autofill.AutofillPopupWindow;
+import android.view.autofill.IAutofillWindowPresenter;
+import android.view.contentcapture.ContentCaptureManager;
+import android.view.contentcapture.ContentCaptureManager.ContentCaptureClient;
+import android.widget.AdapterView;
+import android.widget.Toast;
+import android.widget.Toolbar;
+
+import com.android.internal.R;
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.app.ToolbarActionBar;
+import com.android.internal.app.WindowDecorActionBar;
+import com.android.internal.policy.PhoneWindow;
+
+import dalvik.system.VMRuntime;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+
+/**
+ * An activity is a single, focused thing that the user can do.  Almost all
+ * activities interact with the user, so the Activity class takes care of
+ * creating a window for you in which you can place your UI with
+ * {@link #setContentView}.  While activities are often presented to the user
+ * as full-screen windows, they can also be used in other ways: as floating
+ * windows (via a theme with {@link android.R.attr#windowIsFloating} set),
+ * <a href="https://developer.android.com/guide/topics/ui/multi-window">
+ * Multi-Window mode</a> or embedded into other windows.
+ *
+ * There are two methods almost all subclasses of Activity will implement:
+ *
+ * <ul>
+ *     <li> {@link #onCreate} is where you initialize your activity.  Most
+ *     importantly, here you will usually call {@link #setContentView(int)}
+ *     with a layout resource defining your UI, and using {@link #findViewById}
+ *     to retrieve the widgets in that UI that you need to interact with
+ *     programmatically.
+ *
+ *     <li> {@link #onPause} is where you deal with the user pausing active
+ *     interaction with the activity. Any changes made by the user should at
+ *     this point be committed (usually to the
+ *     {@link android.content.ContentProvider} holding the data). In this
+ *     state the activity is still visible on screen.
+ * </ul>
+ *
+ * <p>To be of use with {@link android.content.Context#startActivity Context.startActivity()}, all
+ * activity classes must have a corresponding
+ * {@link android.R.styleable#AndroidManifestActivity &lt;activity&gt;}
+ * declaration in their package's <code>AndroidManifest.xml</code>.</p>
+ *
+ * <p>Topics covered here:
+ * <ol>
+ * <li><a href="#Fragments">Fragments</a>
+ * <li><a href="#ActivityLifecycle">Activity Lifecycle</a>
+ * <li><a href="#ConfigurationChanges">Configuration Changes</a>
+ * <li><a href="#StartingActivities">Starting Activities and Getting Results</a>
+ * <li><a href="#SavingPersistentState">Saving Persistent State</a>
+ * <li><a href="#Permissions">Permissions</a>
+ * <li><a href="#ProcessLifecycle">Process Lifecycle</a>
+ * </ol>
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>The Activity class is an important part of an application's overall lifecycle,
+ * and the way activities are launched and put together is a fundamental
+ * part of the platform's application model. For a detailed perspective on the structure of an
+ * Android application and how activities behave, please read the
+ * <a href="{@docRoot}guide/topics/fundamentals.html">Application Fundamentals</a> and
+ * <a href="{@docRoot}guide/components/tasks-and-back-stack.html">Tasks and Back Stack</a>
+ * developer guides.</p>
+ *
+ * <p>You can also find a detailed discussion about how to create activities in the
+ * <a href="{@docRoot}guide/components/activities.html">Activities</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * <a name="Fragments"></a>
+ * <h3>Fragments</h3>
+ *
+ * <p>The {@link android.support.v4.app.FragmentActivity} subclass
+ * can make use of the {@link android.support.v4.app.Fragment} class to better
+ * modularize their code, build more sophisticated user interfaces for larger
+ * screens, and help scale their application between small and large screens.</p>
+ *
+ * <p>For more information about using fragments, read the
+ * <a href="{@docRoot}guide/components/fragments.html">Fragments</a> developer guide.</p>
+ *
+ * <a name="ActivityLifecycle"></a>
+ * <h3>Activity Lifecycle</h3>
+ *
+ * <p>Activities in the system are managed as
+ * <a href="https://developer.android.com/guide/components/activities/tasks-and-back-stack">
+ * activity stacks</a>. When a new activity is started, it is usually placed on the top of the
+ * current stack and becomes the running activity -- the previous activity always remains
+ * below it in the stack, and will not come to the foreground again until
+ * the new activity exits. There can be one or multiple activity stacks visible
+ * on screen.</p>
+ *
+ * <p>An activity has essentially four states:</p>
+ * <ul>
+ *     <li>If an activity is in the foreground of the screen (at the highest position of the topmost
+ *         stack), it is <em>active</em> or <em>running</em>. This is usually the activity that the
+ *         user is currently interacting with.</li>
+ *     <li>If an activity has lost focus but is still presented to the user, it is <em>visible</em>.
+ *         It is possible if a new non-full-sized or transparent activity has focus on top of your
+ *         activity, another activity has higher position in multi-window mode, or the activity
+ *         itself is not focusable in current windowing mode. Such activity is completely alive (it
+ *         maintains all state and member information and remains attached to the window manager).
+ *     <li>If an activity is completely obscured by another activity,
+ *         it is <em>stopped</em> or <em>hidden</em>. It still retains all state and member
+ *         information, however, it is no longer visible to the user so its window is hidden
+ *         and it will often be killed by the system when memory is needed elsewhere.</li>
+ *     <li>The system can drop the activity from memory by either asking it to finish,
+ *         or simply killing its process, making it <em>destroyed</em>. When it is displayed again
+ *         to the user, it must be completely restarted and restored to its previous state.</li>
+ * </ul>
+ *
+ * <p>The following diagram shows the important state paths of an Activity.
+ * The square rectangles represent callback methods you can implement to
+ * perform operations when the Activity moves between states.  The colored
+ * ovals are major states the Activity can be in.</p>
+ *
+ * <p><img src="../../../images/activity_lifecycle.png"
+ *      alt="State diagram for an Android Activity Lifecycle." border="0" /></p>
+ *
+ * <p>There are three key loops you may be interested in monitoring within your
+ * activity:
+ *
+ * <ul>
+ * <li>The <b>entire lifetime</b> of an activity happens between the first call
+ * to {@link android.app.Activity#onCreate} through to a single final call
+ * to {@link android.app.Activity#onDestroy}.  An activity will do all setup
+ * of "global" state in onCreate(), and release all remaining resources in
+ * onDestroy().  For example, if it has a thread running in the background
+ * to download data from the network, it may create that thread in onCreate()
+ * and then stop the thread in onDestroy().
+ *
+ * <li>The <b>visible lifetime</b> of an activity happens between a call to
+ * {@link android.app.Activity#onStart} until a corresponding call to
+ * {@link android.app.Activity#onStop}.  During this time the user can see the
+ * activity on-screen, though it may not be in the foreground and interacting
+ * with the user.  Between these two methods you can maintain resources that
+ * are needed to show the activity to the user.  For example, you can register
+ * a {@link android.content.BroadcastReceiver} in onStart() to monitor for changes
+ * that impact your UI, and unregister it in onStop() when the user no
+ * longer sees what you are displaying.  The onStart() and onStop() methods
+ * can be called multiple times, as the activity becomes visible and hidden
+ * to the user.
+ *
+ * <li>The <b>foreground lifetime</b> of an activity happens between a call to
+ * {@link android.app.Activity#onResume} until a corresponding call to
+ * {@link android.app.Activity#onPause}.  During this time the activity is
+ * in visible, active and interacting with the user.  An activity
+ * can frequently go between the resumed and paused states -- for example when
+ * the device goes to sleep, when an activity result is delivered, when a new
+ * intent is delivered -- so the code in these methods should be fairly
+ * lightweight.
+ * </ul>
+ *
+ * <p>The entire lifecycle of an activity is defined by the following
+ * Activity methods.  All of these are hooks that you can override
+ * to do appropriate work when the activity changes state.  All
+ * activities will implement {@link android.app.Activity#onCreate}
+ * to do their initial setup; many will also implement
+ * {@link android.app.Activity#onPause} to commit changes to data and
+ * prepare to pause interacting with the user, and {@link android.app.Activity#onStop}
+ * to handle no longer being visible on screen. You should always
+ * call up to your superclass when implementing these methods.</p>
+ *
+ * </p>
+ * <pre class="prettyprint">
+ * public class Activity extends ApplicationContext {
+ *     protected void onCreate(Bundle savedInstanceState);
+ *
+ *     protected void onStart();
+ *
+ *     protected void onRestart();
+ *
+ *     protected void onResume();
+ *
+ *     protected void onPause();
+ *
+ *     protected void onStop();
+ *
+ *     protected void onDestroy();
+ * }
+ * </pre>
+ *
+ * <p>In general the movement through an activity's lifecycle looks like
+ * this:</p>
+ *
+ * <table border="2" width="85%" align="center" frame="hsides" rules="rows">
+ *     <colgroup align="left" span="3" />
+ *     <colgroup align="left" />
+ *     <colgroup align="center" />
+ *     <colgroup align="center" />
+ *
+ *     <thead>
+ *     <tr><th colspan="3">Method</th> <th>Description</th> <th>Killable?</th> <th>Next</th></tr>
+ *     </thead>
+ *
+ *     <tbody>
+ *     <tr><td colspan="3" align="left" border="0">{@link android.app.Activity#onCreate onCreate()}</td>
+ *         <td>Called when the activity is first created.
+ *             This is where you should do all of your normal static set up:
+ *             create views, bind data to lists, etc.  This method also
+ *             provides you with a Bundle containing the activity's previously
+ *             frozen state, if there was one.
+ *             <p>Always followed by <code>onStart()</code>.</td>
+ *         <td align="center">No</td>
+ *         <td align="center"><code>onStart()</code></td>
+ *     </tr>
+ *
+ *     <tr><td rowspan="5" style="border-left: none; border-right: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ *         <td colspan="2" align="left" border="0">{@link android.app.Activity#onRestart onRestart()}</td>
+ *         <td>Called after your activity has been stopped, prior to it being
+ *             started again.
+ *             <p>Always followed by <code>onStart()</code></td>
+ *         <td align="center">No</td>
+ *         <td align="center"><code>onStart()</code></td>
+ *     </tr>
+ *
+ *     <tr><td colspan="2" align="left" border="0">{@link android.app.Activity#onStart onStart()}</td>
+ *         <td>Called when the activity is becoming visible to the user.
+ *             <p>Followed by <code>onResume()</code> if the activity comes
+ *             to the foreground, or <code>onStop()</code> if it becomes hidden.</td>
+ *         <td align="center">No</td>
+ *         <td align="center"><code>onResume()</code> or <code>onStop()</code></td>
+ *     </tr>
+ *
+ *     <tr><td rowspan="2" style="border-left: none;">&nbsp;&nbsp;&nbsp;&nbsp;</td>
+ *         <td align="left" border="0">{@link android.app.Activity#onResume onResume()}</td>
+ *         <td>Called when the activity will start
+ *             interacting with the user.  At this point your activity is at
+ *             the top of its activity stack, with user input going to it.
+ *             <p>Always followed by <code>onPause()</code>.</td>
+ *         <td align="center">No</td>
+ *         <td align="center"><code>onPause()</code></td>
+ *     </tr>
+ *
+ *     <tr><td align="left" border="0">{@link android.app.Activity#onPause onPause()}</td>
+ *         <td>Called when the activity loses foreground state, is no longer focusable or before
+ *             transition to stopped/hidden or destroyed state. The activity is still visible to
+ *             user, so it's recommended to keep it visually active and continue updating the UI.
+ *             Implementations of this method must be very quick because
+ *             the next activity will not be resumed until this method returns.
+ *             <p>Followed by either <code>onResume()</code> if the activity
+ *             returns back to the front, or <code>onStop()</code> if it becomes
+ *             invisible to the user.</td>
+ *         <td align="center"><font color="#800000"><strong>Pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB}</strong></font></td>
+ *         <td align="center"><code>onResume()</code> or<br>
+ *                 <code>onStop()</code></td>
+ *     </tr>
+ *
+ *     <tr><td colspan="2" align="left" border="0">{@link android.app.Activity#onStop onStop()}</td>
+ *         <td>Called when the activity is no longer visible to the user.  This may happen either
+ *             because a new activity is being started on top, an existing one is being brought in
+ *             front of this one, or this one is being destroyed. This is typically used to stop
+ *             animations and refreshing the UI, etc.
+ *             <p>Followed by either <code>onRestart()</code> if
+ *             this activity is coming back to interact with the user, or
+ *             <code>onDestroy()</code> if this activity is going away.</td>
+ *         <td align="center"><font color="#800000"><strong>Yes</strong></font></td>
+ *         <td align="center"><code>onRestart()</code> or<br>
+ *                 <code>onDestroy()</code></td>
+ *     </tr>
+ *
+ *     <tr><td colspan="3" align="left" border="0">{@link android.app.Activity#onDestroy onDestroy()}</td>
+ *         <td>The final call you receive before your
+ *             activity is destroyed.  This can happen either because the
+ *             activity is finishing (someone called {@link Activity#finish} on
+ *             it), or because the system is temporarily destroying this
+ *             instance of the activity to save space.  You can distinguish
+ *             between these two scenarios with the {@link
+ *             Activity#isFinishing} method.</td>
+ *         <td align="center"><font color="#800000"><strong>Yes</strong></font></td>
+ *         <td align="center"><em>nothing</em></td>
+ *     </tr>
+ *     </tbody>
+ * </table>
+ *
+ * <p>Note the "Killable" column in the above table -- for those methods that
+ * are marked as being killable, after that method returns the process hosting the
+ * activity may be killed by the system <em>at any time</em> without another line
+ * of its code being executed.  Because of this, you should use the
+ * {@link #onPause} method to write any persistent data (such as user edits)
+ * to storage.  In addition, the method
+ * {@link #onSaveInstanceState(Bundle)} is called before placing the activity
+ * in such a background state, allowing you to save away any dynamic instance
+ * state in your activity into the given Bundle, to be later received in
+ * {@link #onCreate} if the activity needs to be re-created.
+ * See the <a href="#ProcessLifecycle">Process Lifecycle</a>
+ * section for more information on how the lifecycle of a process is tied
+ * to the activities it is hosting.  Note that it is important to save
+ * persistent data in {@link #onPause} instead of {@link #onSaveInstanceState}
+ * because the latter is not part of the lifecycle callbacks, so will not
+ * be called in every situation as described in its documentation.</p>
+ *
+ * <p class="note">Be aware that these semantics will change slightly between
+ * applications targeting platforms starting with {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+ * vs. those targeting prior platforms.  Starting with Honeycomb, an application
+ * is not in the killable state until its {@link #onStop} has returned.  This
+ * impacts when {@link #onSaveInstanceState(Bundle)} may be called (it may be
+ * safely called after {@link #onPause()}) and allows an application to safely
+ * wait until {@link #onStop()} to save persistent state.</p>
+ *
+ * <p class="note">For applications targeting platforms starting with
+ * {@link android.os.Build.VERSION_CODES#P} {@link #onSaveInstanceState(Bundle)}
+ * will always be called after {@link #onStop}, so an application may safely
+ * perform fragment transactions in {@link #onStop} and will be able to save
+ * persistent state later.</p>
+ *
+ * <p>For those methods that are not marked as being killable, the activity's
+ * process will not be killed by the system starting from the time the method
+ * is called and continuing after it returns.  Thus an activity is in the killable
+ * state, for example, between after <code>onStop()</code> to the start of
+ * <code>onResume()</code>. Keep in mind that under extreme memory pressure the
+ * system can kill the application process at any time.</p>
+ *
+ * <a name="ConfigurationChanges"></a>
+ * <h3>Configuration Changes</h3>
+ *
+ * <p>If the configuration of the device (as defined by the
+ * {@link Configuration Resources.Configuration} class) changes,
+ * then anything displaying a user interface will need to update to match that
+ * configuration.  Because Activity is the primary mechanism for interacting
+ * with the user, it includes special support for handling configuration
+ * changes.</p>
+ *
+ * <p>Unless you specify otherwise, a configuration change (such as a change
+ * in screen orientation, language, input devices, etc) will cause your
+ * current activity to be <em>destroyed</em>, going through the normal activity
+ * lifecycle process of {@link #onPause},
+ * {@link #onStop}, and {@link #onDestroy} as appropriate.  If the activity
+ * had been in the foreground or visible to the user, once {@link #onDestroy} is
+ * called in that instance then a new instance of the activity will be
+ * created, with whatever savedInstanceState the previous instance had generated
+ * from {@link #onSaveInstanceState}.</p>
+ *
+ * <p>This is done because any application resource,
+ * including layout files, can change based on any configuration value.  Thus
+ * the only safe way to handle a configuration change is to re-retrieve all
+ * resources, including layouts, drawables, and strings.  Because activities
+ * must already know how to save their state and re-create themselves from
+ * that state, this is a convenient way to have an activity restart itself
+ * with a new configuration.</p>
+ *
+ * <p>In some special cases, you may want to bypass restarting of your
+ * activity based on one or more types of configuration changes.  This is
+ * done with the {@link android.R.attr#configChanges android:configChanges}
+ * attribute in its manifest.  For any types of configuration changes you say
+ * that you handle there, you will receive a call to your current activity's
+ * {@link #onConfigurationChanged} method instead of being restarted.  If
+ * a configuration change involves any that you do not handle, however, the
+ * activity will still be restarted and {@link #onConfigurationChanged}
+ * will not be called.</p>
+ *
+ * <a name="StartingActivities"></a>
+ * <h3>Starting Activities and Getting Results</h3>
+ *
+ * <p>The {@link android.app.Activity#startActivity}
+ * method is used to start a
+ * new activity, which will be placed at the top of the activity stack.  It
+ * takes a single argument, an {@link android.content.Intent Intent},
+ * which describes the activity
+ * to be executed.</p>
+ *
+ * <p>Sometimes you want to get a result back from an activity when it
+ * ends.  For example, you may start an activity that lets the user pick
+ * a person in a list of contacts; when it ends, it returns the person
+ * that was selected.  To do this, you call the
+ * {@link android.app.Activity#startActivityForResult(Intent, int)}
+ * version with a second integer parameter identifying the call.  The result
+ * will come back through your {@link android.app.Activity#onActivityResult}
+ * method.</p>
+ *
+ * <p>When an activity exits, it can call
+ * {@link android.app.Activity#setResult(int)}
+ * to return data back to its parent.  It must always supply a result code,
+ * which can be the standard results RESULT_CANCELED, RESULT_OK, or any
+ * custom values starting at RESULT_FIRST_USER.  In addition, it can optionally
+ * return back an Intent containing any additional data it wants.  All of this
+ * information appears back on the
+ * parent's <code>Activity.onActivityResult()</code>, along with the integer
+ * identifier it originally supplied.</p>
+ *
+ * <p>If a child activity fails for any reason (such as crashing), the parent
+ * activity will receive a result with the code RESULT_CANCELED.</p>
+ *
+ * <pre class="prettyprint">
+ * public class MyActivity extends Activity {
+ *     ...
+ *
+ *     static final int PICK_CONTACT_REQUEST = 0;
+ *
+ *     public boolean onKeyDown(int keyCode, KeyEvent event) {
+ *         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+ *             // When the user center presses, let them pick a contact.
+ *             startActivityForResult(
+ *                 new Intent(Intent.ACTION_PICK,
+ *                 new Uri("content://contacts")),
+ *                 PICK_CONTACT_REQUEST);
+ *            return true;
+ *         }
+ *         return false;
+ *     }
+ *
+ *     protected void onActivityResult(int requestCode, int resultCode,
+ *             Intent data) {
+ *         if (requestCode == PICK_CONTACT_REQUEST) {
+ *             if (resultCode == RESULT_OK) {
+ *                 // A contact was picked.  Here we will just display it
+ *                 // to the user.
+ *                 startActivity(new Intent(Intent.ACTION_VIEW, data));
+ *             }
+ *         }
+ *     }
+ * }
+ * </pre>
+ *
+ * <a name="SavingPersistentState"></a>
+ * <h3>Saving Persistent State</h3>
+ *
+ * <p>There are generally two kinds of persistent state that an activity
+ * will deal with: shared document-like data (typically stored in a SQLite
+ * database using a {@linkplain android.content.ContentProvider content provider})
+ * and internal state such as user preferences.</p>
+ *
+ * <p>For content provider data, we suggest that activities use an
+ * "edit in place" user model.  That is, any edits a user makes are effectively
+ * made immediately without requiring an additional confirmation step.
+ * Supporting this model is generally a simple matter of following two rules:</p>
+ *
+ * <ul>
+ *     <li> <p>When creating a new document, the backing database entry or file for
+ *             it is created immediately.  For example, if the user chooses to write
+ *             a new email, a new entry for that email is created as soon as they
+ *             start entering data, so that if they go to any other activity after
+ *             that point this email will now appear in the list of drafts.</p>
+ *     <li> <p>When an activity's <code>onPause()</code> method is called, it should
+ *             commit to the backing content provider or file any changes the user
+ *             has made.  This ensures that those changes will be seen by any other
+ *             activity that is about to run.  You will probably want to commit
+ *             your data even more aggressively at key times during your
+ *             activity's lifecycle: for example before starting a new
+ *             activity, before finishing your own activity, when the user
+ *             switches between input fields, etc.</p>
+ * </ul>
+ *
+ * <p>This model is designed to prevent data loss when a user is navigating
+ * between activities, and allows the system to safely kill an activity (because
+ * system resources are needed somewhere else) at any time after it has been
+ * stopped (or paused on platform versions before {@link android.os.Build.VERSION_CODES#HONEYCOMB}).
+ * Note this implies that the user pressing BACK from your activity does <em>not</em>
+ * mean "cancel" -- it means to leave the activity with its current contents
+ * saved away.  Canceling edits in an activity must be provided through
+ * some other mechanism, such as an explicit "revert" or "undo" option.</p>
+ *
+ * <p>See the {@linkplain android.content.ContentProvider content package} for
+ * more information about content providers.  These are a key aspect of how
+ * different activities invoke and propagate data between themselves.</p>
+ *
+ * <p>The Activity class also provides an API for managing internal persistent state
+ * associated with an activity.  This can be used, for example, to remember
+ * the user's preferred initial display in a calendar (day view or week view)
+ * or the user's default home page in a web browser.</p>
+ *
+ * <p>Activity persistent state is managed
+ * with the method {@link #getPreferences},
+ * allowing you to retrieve and
+ * modify a set of name/value pairs associated with the activity.  To use
+ * preferences that are shared across multiple application components
+ * (activities, receivers, services, providers), you can use the underlying
+ * {@link Context#getSharedPreferences Context.getSharedPreferences()} method
+ * to retrieve a preferences
+ * object stored under a specific name.
+ * (Note that it is not possible to share settings data across application
+ * packages -- for that you will need a content provider.)</p>
+ *
+ * <p>Here is an excerpt from a calendar activity that stores the user's
+ * preferred view mode in its persistent settings:</p>
+ *
+ * <pre class="prettyprint">
+ * public class CalendarActivity extends Activity {
+ *     ...
+ *
+ *     static final int DAY_VIEW_MODE = 0;
+ *     static final int WEEK_VIEW_MODE = 1;
+ *
+ *     private SharedPreferences mPrefs;
+ *     private int mCurViewMode;
+ *
+ *     protected void onCreate(Bundle savedInstanceState) {
+ *         super.onCreate(savedInstanceState);
+ *
+ *         SharedPreferences mPrefs = getSharedPreferences();
+ *         mCurViewMode = mPrefs.getInt("view_mode", DAY_VIEW_MODE);
+ *     }
+ *
+ *     protected void onPause() {
+ *         super.onPause();
+ *
+ *         SharedPreferences.Editor ed = mPrefs.edit();
+ *         ed.putInt("view_mode", mCurViewMode);
+ *         ed.commit();
+ *     }
+ * }
+ * </pre>
+ *
+ * <a name="Permissions"></a>
+ * <h3>Permissions</h3>
+ *
+ * <p>The ability to start a particular Activity can be enforced when it is
+ * declared in its
+ * manifest's {@link android.R.styleable#AndroidManifestActivity &lt;activity&gt;}
+ * tag.  By doing so, other applications will need to declare a corresponding
+ * {@link android.R.styleable#AndroidManifestUsesPermission &lt;uses-permission&gt;}
+ * element in their own manifest to be able to start that activity.
+ *
+ * <p>When starting an Activity you can set {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
+ * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+ * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} on the Intent.  This will grant the
+ * Activity access to the specific URIs in the Intent.  Access will remain
+ * until the Activity has finished (it will remain across the hosting
+ * process being killed and other temporary destruction).  As of
+ * {@link android.os.Build.VERSION_CODES#GINGERBREAD}, if the Activity
+ * was already created and a new Intent is being delivered to
+ * {@link #onNewIntent(Intent)}, any newly granted URI permissions will be added
+ * to the existing ones it holds.
+ *
+ * <p>See the <a href="{@docRoot}guide/topics/security/security.html">Security and Permissions</a>
+ * document for more information on permissions and security in general.
+ *
+ * <a name="ProcessLifecycle"></a>
+ * <h3>Process Lifecycle</h3>
+ *
+ * <p>The Android system attempts to keep an application process around for as
+ * long as possible, but eventually will need to remove old processes when
+ * memory runs low. As described in <a href="#ActivityLifecycle">Activity
+ * Lifecycle</a>, the decision about which process to remove is intimately
+ * tied to the state of the user's interaction with it. In general, there
+ * are four states a process can be in based on the activities running in it,
+ * listed here in order of importance. The system will kill less important
+ * processes (the last ones) before it resorts to killing more important
+ * processes (the first ones).
+ *
+ * <ol>
+ * <li> <p>The <b>foreground activity</b> (the activity at the top of the screen
+ * that the user is currently interacting with) is considered the most important.
+ * Its process will only be killed as a last resort, if it uses more memory
+ * than is available on the device.  Generally at this point the device has
+ * reached a memory paging state, so this is required in order to keep the user
+ * interface responsive.
+ * <li> <p>A <b>visible activity</b> (an activity that is visible to the user
+ * but not in the foreground, such as one sitting behind a foreground dialog
+ * or next to other activities in multi-window mode)
+ * is considered extremely important and will not be killed unless that is
+ * required to keep the foreground activity running.
+ * <li> <p>A <b>background activity</b> (an activity that is not visible to
+ * the user and has been stopped) is no longer critical, so the system may
+ * safely kill its process to reclaim memory for other foreground or
+ * visible processes.  If its process needs to be killed, when the user navigates
+ * back to the activity (making it visible on the screen again), its
+ * {@link #onCreate} method will be called with the savedInstanceState it had previously
+ * supplied in {@link #onSaveInstanceState} so that it can restart itself in the same
+ * state as the user last left it.
+ * <li> <p>An <b>empty process</b> is one hosting no activities or other
+ * application components (such as {@link Service} or
+ * {@link android.content.BroadcastReceiver} classes).  These are killed very
+ * quickly by the system as memory becomes low.  For this reason, any
+ * background operation you do outside of an activity must be executed in the
+ * context of an activity BroadcastReceiver or Service to ensure that the system
+ * knows it needs to keep your process around.
+ * </ol>
+ *
+ * <p>Sometimes an Activity may need to do a long-running operation that exists
+ * independently of the activity lifecycle itself.  An example may be a camera
+ * application that allows you to upload a picture to a web site.  The upload
+ * may take a long time, and the application should allow the user to leave
+ * the application while it is executing.  To accomplish this, your Activity
+ * should start a {@link Service} in which the upload takes place.  This allows
+ * the system to properly prioritize your process (considering it to be more
+ * important than other non-visible applications) for the duration of the
+ * upload, independent of whether the original activity is paused, stopped,
+ * or finished.
+ */
+public class Activity extends ContextThemeWrapper
+        implements LayoutInflater.Factory2,
+        Window.Callback, KeyEvent.Callback,
+        OnCreateContextMenuListener, ComponentCallbacks2,
+        Window.OnWindowDismissedCallback, WindowControllerCallback,
+        AutofillManager.AutofillClient, ContentCaptureManager.ContentCaptureClient {
+    private static final String TAG = "Activity";
+    private static final boolean DEBUG_LIFECYCLE = false;
+
+    /** Standard activity result: operation canceled. */
+    public static final int RESULT_CANCELED    = 0;
+    /** Standard activity result: operation succeeded. */
+    public static final int RESULT_OK           = -1;
+    /** Start of user-defined activity results. */
+    public static final int RESULT_FIRST_USER   = 1;
+
+    /** @hide Task isn't finished when activity is finished */
+    public static final int DONT_FINISH_TASK_WITH_ACTIVITY = 0;
+    /**
+     * @hide Task is finished if the finishing activity is the root of the task. To preserve the
+     * past behavior the task is also removed from recents.
+     */
+    public static final int FINISH_TASK_WITH_ROOT_ACTIVITY = 1;
+    /**
+     * @hide Task is finished along with the finishing activity, but it is not removed from
+     * recents.
+     */
+    public static final int FINISH_TASK_WITH_ACTIVITY = 2;
+
+    @UnsupportedAppUsage
+    static final String FRAGMENTS_TAG = "android:fragments";
+    private static final String LAST_AUTOFILL_ID = "android:lastAutofillId";
+
+    private static final String AUTOFILL_RESET_NEEDED = "@android:autofillResetNeeded";
+    private static final String WINDOW_HIERARCHY_TAG = "android:viewHierarchyState";
+    private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds";
+    private static final String SAVED_DIALOGS_TAG = "android:savedDialogs";
+    private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_";
+    private static final String SAVED_DIALOG_ARGS_KEY_PREFIX = "android:dialog_args_";
+    private static final String HAS_CURENT_PERMISSIONS_REQUEST_KEY =
+            "android:hasCurrentPermissionsRequest";
+
+    private static final String REQUEST_PERMISSIONS_WHO_PREFIX = "@android:requestPermissions:";
+    private static final String AUTO_FILL_AUTH_WHO_PREFIX = "@android:autoFillAuth:";
+
+    private static final String KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME = "com.android.systemui";
+
+    private static final int LOG_AM_ON_CREATE_CALLED = 30057;
+    private static final int LOG_AM_ON_START_CALLED = 30059;
+    private static final int LOG_AM_ON_RESUME_CALLED = 30022;
+    private static final int LOG_AM_ON_PAUSE_CALLED = 30021;
+    private static final int LOG_AM_ON_STOP_CALLED = 30049;
+    private static final int LOG_AM_ON_RESTART_CALLED = 30058;
+    private static final int LOG_AM_ON_DESTROY_CALLED = 30060;
+    private static final int LOG_AM_ON_ACTIVITY_RESULT_CALLED = 30062;
+    private static final int LOG_AM_ON_TOP_RESUMED_GAINED_CALLED = 30064;
+    private static final int LOG_AM_ON_TOP_RESUMED_LOST_CALLED = 30065;
+
+    private static class ManagedDialog {
+        Dialog mDialog;
+        Bundle mArgs;
+    }
+    private SparseArray<ManagedDialog> mManagedDialogs;
+
+    // set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called.
+    @UnsupportedAppUsage
+    private Instrumentation mInstrumentation;
+    @UnsupportedAppUsage
+    private IBinder mToken;
+    private IBinder mAssistToken;
+    @UnsupportedAppUsage
+    private int mIdent;
+    @UnsupportedAppUsage
+    /*package*/ String mEmbeddedID;
+    @UnsupportedAppUsage
+    private Application mApplication;
+    @UnsupportedAppUsage
+    /*package*/ Intent mIntent;
+    @UnsupportedAppUsage
+    /*package*/ String mReferrer;
+    @UnsupportedAppUsage
+    private ComponentName mComponent;
+    @UnsupportedAppUsage
+    /*package*/ ActivityInfo mActivityInfo;
+    @UnsupportedAppUsage
+    /*package*/ ActivityThread mMainThread;
+    @UnsupportedAppUsage
+    Activity mParent;
+    @UnsupportedAppUsage
+    boolean mCalled;
+    @UnsupportedAppUsage
+    /*package*/ boolean mResumed;
+    @UnsupportedAppUsage
+    /*package*/ boolean mStopped;
+    @UnsupportedAppUsage
+    boolean mFinished;
+    boolean mStartedActivity;
+    @UnsupportedAppUsage
+    private boolean mDestroyed;
+    private boolean mDoReportFullyDrawn = true;
+    private boolean mRestoredFromBundle;
+
+    /** {@code true} if the activity lifecycle is in a state which supports picture-in-picture.
+     * This only affects the client-side exception, the actual state check still happens in AMS. */
+    private boolean mCanEnterPictureInPicture = false;
+    /** true if the activity is being destroyed in order to recreate it with a new configuration */
+    /*package*/ boolean mChangingConfigurations = false;
+    @UnsupportedAppUsage
+    /*package*/ int mConfigChangeFlags;
+    @UnsupportedAppUsage
+    /*package*/ Configuration mCurrentConfig;
+    private SearchManager mSearchManager;
+    private MenuInflater mMenuInflater;
+
+    /** The autofill manager. Always access via {@link #getAutofillManager()}. */
+    @Nullable private AutofillManager mAutofillManager;
+
+    /** The content capture manager. Access via {@link #getContentCaptureManager()}. */
+    @Nullable private ContentCaptureManager mContentCaptureManager;
+
+    private final ArrayList<Application.ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
+            new ArrayList<Application.ActivityLifecycleCallbacks>();
+
+    static final class NonConfigurationInstances {
+        Object activity;
+        HashMap<String, Object> children;
+        FragmentManagerNonConfig fragments;
+        ArrayMap<String, LoaderManager> loaders;
+        VoiceInteractor voiceInteractor;
+    }
+    @UnsupportedAppUsage
+    /* package */ NonConfigurationInstances mLastNonConfigurationInstances;
+
+    @UnsupportedAppUsage
+    private Window mWindow;
+
+    @UnsupportedAppUsage
+    private WindowManager mWindowManager;
+    /*package*/ View mDecor = null;
+    @UnsupportedAppUsage
+    /*package*/ boolean mWindowAdded = false;
+    /*package*/ boolean mVisibleFromServer = false;
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    /*package*/ boolean mVisibleFromClient = true;
+    /*package*/ ActionBar mActionBar = null;
+    private boolean mEnableDefaultActionBarUp;
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    VoiceInteractor mVoiceInteractor;
+
+    @UnsupportedAppUsage
+    private CharSequence mTitle;
+    private int mTitleColor = 0;
+
+    // we must have a handler before the FragmentController is constructed
+    @UnsupportedAppUsage
+    final Handler mHandler = new Handler();
+    @UnsupportedAppUsage
+    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
+
+    private static final class ManagedCursor {
+        ManagedCursor(Cursor cursor) {
+            mCursor = cursor;
+            mReleased = false;
+            mUpdated = false;
+        }
+
+        private final Cursor mCursor;
+        private boolean mReleased;
+        private boolean mUpdated;
+    }
+
+    @GuardedBy("mManagedCursors")
+    private final ArrayList<ManagedCursor> mManagedCursors = new ArrayList<>();
+
+    @GuardedBy("this")
+    @UnsupportedAppUsage
+    int mResultCode = RESULT_CANCELED;
+    @GuardedBy("this")
+    @UnsupportedAppUsage
+    Intent mResultData = null;
+
+    private TranslucentConversionListener mTranslucentCallback;
+    private boolean mChangeCanvasToTranslucent;
+
+    private SearchEvent mSearchEvent;
+
+    private boolean mTitleReady = false;
+    private int mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
+
+    private int mDefaultKeyMode = DEFAULT_KEYS_DISABLE;
+    private SpannableStringBuilder mDefaultKeySsb = null;
+
+    private ActivityManager.TaskDescription mTaskDescription =
+            new ActivityManager.TaskDescription();
+
+    protected static final int[] FOCUSED_STATE_SET = {com.android.internal.R.attr.state_focused};
+
+    @SuppressWarnings("unused")
+    private final Object mInstanceTracker = StrictMode.trackActivity(this);
+
+    private Thread mUiThread;
+
+    @UnsupportedAppUsage
+    ActivityTransitionState mActivityTransitionState = new ActivityTransitionState();
+    SharedElementCallback mEnterTransitionListener = SharedElementCallback.NULL_CALLBACK;
+    SharedElementCallback mExitTransitionListener = SharedElementCallback.NULL_CALLBACK;
+
+    private boolean mHasCurrentPermissionsRequest;
+
+    private boolean mAutoFillResetNeeded;
+    private boolean mAutoFillIgnoreFirstResumePause;
+
+    /** The last autofill id that was returned from {@link #getNextAutofillId()} */
+    private int mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
+
+    private AutofillPopupWindow mAutofillPopupWindow;
+
+    /** @hide */
+    boolean mEnterAnimationComplete;
+
+    private static native String getDlWarning();
+
+    /** Return the intent that started this activity. */
+    public Intent getIntent() {
+        return mIntent;
+    }
+
+    /**
+     * Change the intent returned by {@link #getIntent}.  This holds a
+     * reference to the given intent; it does not copy it.  Often used in
+     * conjunction with {@link #onNewIntent}.
+     *
+     * @param newIntent The new Intent object to return from getIntent
+     *
+     * @see #getIntent
+     * @see #onNewIntent
+     */
+    public void setIntent(Intent newIntent) {
+        mIntent = newIntent;
+    }
+
+    /** Return the application that owns this activity. */
+    public final Application getApplication() {
+        return mApplication;
+    }
+
+    /** Is this activity embedded inside of another activity? */
+    public final boolean isChild() {
+        return mParent != null;
+    }
+
+    /** Return the parent activity if this view is an embedded child. */
+    public final Activity getParent() {
+        return mParent;
+    }
+
+    /** Retrieve the window manager for showing custom windows. */
+    public WindowManager getWindowManager() {
+        return mWindowManager;
+    }
+
+    /**
+     * Retrieve the current {@link android.view.Window} for the activity.
+     * This can be used to directly access parts of the Window API that
+     * are not available through Activity/Screen.
+     *
+     * @return Window The current window, or null if the activity is not
+     *         visual.
+     */
+    public Window getWindow() {
+        return mWindow;
+    }
+
+    /**
+     * Return the LoaderManager for this activity, creating it if needed.
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportLoaderManager()}
+     */
+    @Deprecated
+    public LoaderManager getLoaderManager() {
+        return mFragments.getLoaderManager();
+    }
+
+    /**
+     * Calls {@link android.view.Window#getCurrentFocus} on the
+     * Window of this Activity to return the currently focused view.
+     *
+     * @return View The current View with focus or null.
+     *
+     * @see #getWindow
+     * @see android.view.Window#getCurrentFocus
+     */
+    @Nullable
+    public View getCurrentFocus() {
+        return mWindow != null ? mWindow.getCurrentFocus() : null;
+    }
+
+    /**
+     * (Creates, sets and) returns the autofill manager
+     *
+     * @return The autofill manager
+     */
+    @NonNull private AutofillManager getAutofillManager() {
+        if (mAutofillManager == null) {
+            mAutofillManager = getSystemService(AutofillManager.class);
+        }
+
+        return mAutofillManager;
+    }
+
+    /**
+     * (Creates, sets, and ) returns the content capture manager
+     *
+     * @return The content capture manager
+     */
+    @Nullable private ContentCaptureManager getContentCaptureManager() {
+        // ContextCapture disabled for system apps
+        if (!UserHandle.isApp(myUid())) return null;
+        if (mContentCaptureManager == null) {
+            mContentCaptureManager = getSystemService(ContentCaptureManager.class);
+        }
+        return mContentCaptureManager;
+    }
+
+    /** @hide */ private static final int CONTENT_CAPTURE_START = 1;
+    /** @hide */ private static final int CONTENT_CAPTURE_RESUME = 2;
+    /** @hide */ private static final int CONTENT_CAPTURE_PAUSE = 3;
+    /** @hide */ private static final int CONTENT_CAPTURE_STOP = 4;
+
+    /** @hide */
+    @IntDef(prefix = { "CONTENT_CAPTURE_" }, value = {
+            CONTENT_CAPTURE_START,
+            CONTENT_CAPTURE_RESUME,
+            CONTENT_CAPTURE_PAUSE,
+            CONTENT_CAPTURE_STOP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface ContentCaptureNotificationType{}
+
+    private String getContentCaptureTypeAsString(@ContentCaptureNotificationType int type) {
+        switch (type) {
+            case CONTENT_CAPTURE_START:
+                return "START";
+            case CONTENT_CAPTURE_RESUME:
+                return "RESUME";
+            case CONTENT_CAPTURE_PAUSE:
+                return "PAUSE";
+            case CONTENT_CAPTURE_STOP:
+                return "STOP";
+            default:
+                return "UNKNOW-" + type;
+        }
+    }
+
+    private void notifyContentCaptureManagerIfNeeded(@ContentCaptureNotificationType int type) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_ACTIVITY_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                    "notifyContentCapture(" + getContentCaptureTypeAsString(type) + ") for "
+                            + mComponent.toShortString());
+        }
+        try {
+            final ContentCaptureManager cm = getContentCaptureManager();
+            if (cm == null) return;
+
+            switch (type) {
+                case CONTENT_CAPTURE_START:
+                    //TODO(b/111276913): decide whether the InteractionSessionId should be
+                    // saved / restored in the activity bundle - probably not
+                    final Window window = getWindow();
+                    if (window != null) {
+                        cm.updateWindowAttributes(window.getAttributes());
+                    }
+                    cm.onActivityCreated(mToken, getComponentName());
+                    break;
+                case CONTENT_CAPTURE_RESUME:
+                    cm.onActivityResumed();
+                    break;
+                case CONTENT_CAPTURE_PAUSE:
+                    cm.onActivityPaused();
+                    break;
+                case CONTENT_CAPTURE_STOP:
+                    cm.onActivityDestroyed();
+                    break;
+                default:
+                    Log.wtf(TAG, "Invalid @ContentCaptureNotificationType: " + type);
+            }
+        } finally {
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        }
+    }
+
+    @Override
+    protected void attachBaseContext(Context newBase) {
+        super.attachBaseContext(newBase);
+        if (newBase != null) {
+            newBase.setAutofillClient(this);
+            newBase.setContentCaptureOptions(getContentCaptureOptions());
+        }
+    }
+
+    /** @hide */
+    @Override
+    public final AutofillClient getAutofillClient() {
+        return this;
+    }
+
+    /** @hide */
+    @Override
+    public final ContentCaptureClient getContentCaptureClient() {
+        return this;
+    }
+
+    /**
+     * Register an {@link Application.ActivityLifecycleCallbacks} instance that receives
+     * lifecycle callbacks for only this Activity.
+     * <p>
+     * In relation to any
+     * {@link Application#registerActivityLifecycleCallbacks Application registered callbacks},
+     * the callbacks registered here will always occur nested within those callbacks. This means:
+     * <ul>
+     *     <li>Pre events will first be sent to Application registered callbacks, then to callbacks
+     *     registered here.</li>
+     *     <li>{@link Application.ActivityLifecycleCallbacks#onActivityCreated(Activity, Bundle)},
+     *     {@link Application.ActivityLifecycleCallbacks#onActivityStarted(Activity)}, and
+     *     {@link Application.ActivityLifecycleCallbacks#onActivityResumed(Activity)} will
+     *     be sent first to Application registered callbacks, then to callbacks registered here.
+     *     For all other events, callbacks registered here will be sent first.</li>
+     *     <li>Post events will first be sent to callbacks registered here, then to
+     *     Application registered callbacks.</li>
+     * </ul>
+     * <p>
+     * If multiple callbacks are registered here, they receive events in a first in (up through
+     * {@link Application.ActivityLifecycleCallbacks#onActivityPostResumed}, last out
+     * ordering.
+     * <p>
+     * It is strongly recommended to register this in the constructor of your Activity to ensure
+     * you get all available callbacks. As this callback is associated with only this Activity,
+     * it is not usually necessary to {@link #unregisterActivityLifecycleCallbacks unregister} it
+     * unless you specifically do not want to receive further lifecycle callbacks.
+     *
+     * @param callback The callback instance to register
+     */
+    public void registerActivityLifecycleCallbacks(
+            @NonNull Application.ActivityLifecycleCallbacks callback) {
+        synchronized (mActivityLifecycleCallbacks) {
+            mActivityLifecycleCallbacks.add(callback);
+        }
+    }
+
+    /**
+     * Unregister an {@link Application.ActivityLifecycleCallbacks} previously registered
+     * with {@link #registerActivityLifecycleCallbacks}. It will not receive any further
+     * callbacks.
+     *
+     * @param callback The callback instance to unregister
+     * @see #registerActivityLifecycleCallbacks
+     */
+    public void unregisterActivityLifecycleCallbacks(
+            @NonNull Application.ActivityLifecycleCallbacks callback) {
+        synchronized (mActivityLifecycleCallbacks) {
+            mActivityLifecycleCallbacks.remove(callback);
+        }
+    }
+
+    private void dispatchActivityPreCreated(@Nullable Bundle savedInstanceState) {
+        getApplication().dispatchActivityPreCreated(this, savedInstanceState);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPreCreated(this,
+                        savedInstanceState);
+            }
+        }
+    }
+
+    private void dispatchActivityCreated(@Nullable Bundle savedInstanceState) {
+        getApplication().dispatchActivityCreated(this, savedInstanceState);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityCreated(this,
+                        savedInstanceState);
+            }
+        }
+    }
+
+    private void dispatchActivityPostCreated(@Nullable Bundle savedInstanceState) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPostCreated(this,
+                        savedInstanceState);
+            }
+        }
+        getApplication().dispatchActivityPostCreated(this, savedInstanceState);
+    }
+
+    private void dispatchActivityPreStarted() {
+        getApplication().dispatchActivityPreStarted(this);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPreStarted(this);
+            }
+        }
+    }
+
+    private void dispatchActivityStarted() {
+        getApplication().dispatchActivityStarted(this);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityStarted(this);
+            }
+        }
+    }
+
+    private void dispatchActivityPostStarted() {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i])
+                        .onActivityPostStarted(this);
+            }
+        }
+        getApplication().dispatchActivityPostStarted(this);
+    }
+
+    private void dispatchActivityPreResumed() {
+        getApplication().dispatchActivityPreResumed(this);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPreResumed(this);
+            }
+        }
+    }
+
+    private void dispatchActivityResumed() {
+        getApplication().dispatchActivityResumed(this);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityResumed(this);
+            }
+        }
+    }
+
+    private void dispatchActivityPostResumed() {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPostResumed(this);
+            }
+        }
+        getApplication().dispatchActivityPostResumed(this);
+    }
+
+    private void dispatchActivityPrePaused() {
+        getApplication().dispatchActivityPrePaused(this);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPrePaused(this);
+            }
+        }
+    }
+
+    private void dispatchActivityPaused() {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPaused(this);
+            }
+        }
+        getApplication().dispatchActivityPaused(this);
+    }
+
+    private void dispatchActivityPostPaused() {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPostPaused(this);
+            }
+        }
+        getApplication().dispatchActivityPostPaused(this);
+    }
+
+    private void dispatchActivityPreStopped() {
+        getApplication().dispatchActivityPreStopped(this);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityPreStopped(this);
+            }
+        }
+    }
+
+    private void dispatchActivityStopped() {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityStopped(this);
+            }
+        }
+        getApplication().dispatchActivityStopped(this);
+    }
+
+    private void dispatchActivityPostStopped() {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i])
+                        .onActivityPostStopped(this);
+            }
+        }
+        getApplication().dispatchActivityPostStopped(this);
+    }
+
+    private void dispatchActivityPreSaveInstanceState(@NonNull Bundle outState) {
+        getApplication().dispatchActivityPreSaveInstanceState(this, outState);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i])
+                        .onActivityPreSaveInstanceState(this, outState);
+            }
+        }
+    }
+
+    private void dispatchActivitySaveInstanceState(@NonNull Bundle outState) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i])
+                        .onActivitySaveInstanceState(this, outState);
+            }
+        }
+        getApplication().dispatchActivitySaveInstanceState(this, outState);
+    }
+
+    private void dispatchActivityPostSaveInstanceState(@NonNull Bundle outState) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i])
+                        .onActivityPostSaveInstanceState(this, outState);
+            }
+        }
+        getApplication().dispatchActivityPostSaveInstanceState(this, outState);
+    }
+
+    private void dispatchActivityPreDestroyed() {
+        getApplication().dispatchActivityPreDestroyed(this);
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i])
+                        .onActivityPreDestroyed(this);
+            }
+        }
+    }
+
+    private void dispatchActivityDestroyed() {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i]).onActivityDestroyed(this);
+            }
+        }
+        getApplication().dispatchActivityDestroyed(this);
+    }
+
+    private void dispatchActivityPostDestroyed() {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = callbacks.length - 1; i >= 0; i--) {
+                ((Application.ActivityLifecycleCallbacks) callbacks[i])
+                        .onActivityPostDestroyed(this);
+            }
+        }
+        getApplication().dispatchActivityPostDestroyed(this);
+    }
+
+    private Object[] collectActivityLifecycleCallbacks() {
+        Object[] callbacks = null;
+        synchronized (mActivityLifecycleCallbacks) {
+            if (mActivityLifecycleCallbacks.size() > 0) {
+                callbacks = mActivityLifecycleCallbacks.toArray();
+            }
+        }
+        return callbacks;
+    }
+
+    /**
+     * Called when the activity is starting.  This is where most initialization
+     * should go: calling {@link #setContentView(int)} to inflate the
+     * activity's UI, using {@link #findViewById} to programmatically interact
+     * with widgets in the UI, calling
+     * {@link #managedQuery(android.net.Uri , String[], String, String[], String)} to retrieve
+     * cursors for data being displayed, etc.
+     *
+     * <p>You can call {@link #finish} from within this function, in
+     * which case onDestroy() will be immediately called after {@link #onCreate} without any of the
+     * rest of the activity lifecycle ({@link #onStart}, {@link #onResume}, {@link #onPause}, etc)
+     * executing.
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     *
+     * @param savedInstanceState If the activity is being re-initialized after
+     *     previously being shut down then this Bundle contains the data it most
+     *     recently supplied in {@link #onSaveInstanceState}.  <b><i>Note: Otherwise it is null.</i></b>
+     *
+     * @see #onStart
+     * @see #onSaveInstanceState
+     * @see #onRestoreInstanceState
+     * @see #onPostCreate
+     */
+    @MainThread
+    @CallSuper
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onCreate " + this + ": " + savedInstanceState);
+
+        if (mLastNonConfigurationInstances != null) {
+            mFragments.restoreLoaderNonConfig(mLastNonConfigurationInstances.loaders);
+        }
+        if (mActivityInfo.parentActivityName != null) {
+            if (mActionBar == null) {
+                mEnableDefaultActionBarUp = true;
+            } else {
+                mActionBar.setDefaultDisplayHomeAsUpEnabled(true);
+            }
+        }
+        if (savedInstanceState != null) {
+            mAutoFillResetNeeded = savedInstanceState.getBoolean(AUTOFILL_RESET_NEEDED, false);
+            mLastAutofillId = savedInstanceState.getInt(LAST_AUTOFILL_ID,
+                    View.LAST_APP_AUTOFILL_ID);
+
+            if (mAutoFillResetNeeded) {
+                getAutofillManager().onCreate(savedInstanceState);
+            }
+
+            Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
+            mFragments.restoreAllState(p, mLastNonConfigurationInstances != null
+                    ? mLastNonConfigurationInstances.fragments : null);
+        }
+        mFragments.dispatchCreate();
+        dispatchActivityCreated(savedInstanceState);
+        if (mVoiceInteractor != null) {
+            mVoiceInteractor.attachActivity(this);
+        }
+        mRestoredFromBundle = savedInstanceState != null;
+        mCalled = true;
+
+    }
+
+    /**
+     * Same as {@link #onCreate(android.os.Bundle)} but called for those activities created with
+     * the attribute {@link android.R.attr#persistableMode} set to
+     * <code>persistAcrossReboots</code>.
+     *
+     * @param savedInstanceState if the activity is being re-initialized after
+     *     previously being shut down then this Bundle contains the data it most
+     *     recently supplied in {@link #onSaveInstanceState}.
+     *     <b><i>Note: Otherwise it is null.</i></b>
+     * @param persistentState if the activity is being re-initialized after
+     *     previously being shut down or powered off then this Bundle contains the data it most
+     *     recently supplied to outPersistentState in {@link #onSaveInstanceState}.
+     *     <b><i>Note: Otherwise it is null.</i></b>
+     *
+     * @see #onCreate(android.os.Bundle)
+     * @see #onStart
+     * @see #onSaveInstanceState
+     * @see #onRestoreInstanceState
+     * @see #onPostCreate
+     */
+    public void onCreate(@Nullable Bundle savedInstanceState,
+            @Nullable PersistableBundle persistentState) {
+        onCreate(savedInstanceState);
+    }
+
+    /**
+     * The hook for {@link ActivityThread} to restore the state of this activity.
+     *
+     * Calls {@link #onSaveInstanceState(android.os.Bundle)} and
+     * {@link #restoreManagedDialogs(android.os.Bundle)}.
+     *
+     * @param savedInstanceState contains the saved state
+     */
+    final void performRestoreInstanceState(@NonNull Bundle savedInstanceState) {
+        onRestoreInstanceState(savedInstanceState);
+        restoreManagedDialogs(savedInstanceState);
+    }
+
+    /**
+     * The hook for {@link ActivityThread} to restore the state of this activity.
+     *
+     * Calls {@link #onSaveInstanceState(android.os.Bundle)} and
+     * {@link #restoreManagedDialogs(android.os.Bundle)}.
+     *
+     * @param savedInstanceState contains the saved state
+     * @param persistentState contains the persistable saved state
+     */
+    final void performRestoreInstanceState(@Nullable Bundle savedInstanceState,
+            @Nullable PersistableBundle persistentState) {
+        onRestoreInstanceState(savedInstanceState, persistentState);
+        if (savedInstanceState != null) {
+            restoreManagedDialogs(savedInstanceState);
+        }
+    }
+
+    /**
+     * This method is called after {@link #onStart} when the activity is
+     * being re-initialized from a previously saved state, given here in
+     * <var>savedInstanceState</var>.  Most implementations will simply use {@link #onCreate}
+     * to restore their state, but it is sometimes convenient to do it here
+     * after all of the initialization has been done or to allow subclasses to
+     * decide whether to use your default implementation.  The default
+     * implementation of this method performs a restore of any view state that
+     * had previously been frozen by {@link #onSaveInstanceState}.
+     *
+     * <p>This method is called between {@link #onStart} and
+     * {@link #onPostCreate}.
+     *
+     * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}.
+     *
+     * @see #onCreate
+     * @see #onPostCreate
+     * @see #onResume
+     * @see #onSaveInstanceState
+     */
+    protected void onRestoreInstanceState(@NonNull Bundle savedInstanceState) {
+        if (mWindow != null) {
+            Bundle windowState = savedInstanceState.getBundle(WINDOW_HIERARCHY_TAG);
+            if (windowState != null) {
+                mWindow.restoreHierarchyState(windowState);
+            }
+        }
+    }
+
+    /**
+     * This is the same as {@link #onRestoreInstanceState(Bundle)} but is called for activities
+     * created with the attribute {@link android.R.attr#persistableMode} set to
+     * <code>persistAcrossReboots</code>. The {@link android.os.PersistableBundle} passed
+     * came from the restored PersistableBundle first
+     * saved in {@link #onSaveInstanceState(Bundle, PersistableBundle)}.
+     *
+     * <p>This method is called between {@link #onStart} and
+     * {@link #onPostCreate}.
+     *
+     * <p>If this method is called {@link #onRestoreInstanceState(Bundle)} will not be called.
+     *
+     * <p>At least one of {@code savedInstanceState} or {@code persistentState} will not be null.
+     *
+     * @param savedInstanceState the data most recently supplied in {@link #onSaveInstanceState}
+     *     or null.
+     * @param persistentState the data most recently supplied in {@link #onSaveInstanceState}
+     *     or null.
+     *
+     * @see #onRestoreInstanceState(Bundle)
+     * @see #onCreate
+     * @see #onPostCreate
+     * @see #onResume
+     * @see #onSaveInstanceState
+     */
+    public void onRestoreInstanceState(@Nullable Bundle savedInstanceState,
+            @Nullable PersistableBundle persistentState) {
+        if (savedInstanceState != null) {
+            onRestoreInstanceState(savedInstanceState);
+        }
+    }
+
+    /**
+     * Restore the state of any saved managed dialogs.
+     *
+     * @param savedInstanceState The bundle to restore from.
+     */
+    private void restoreManagedDialogs(Bundle savedInstanceState) {
+        final Bundle b = savedInstanceState.getBundle(SAVED_DIALOGS_TAG);
+        if (b == null) {
+            return;
+        }
+
+        final int[] ids = b.getIntArray(SAVED_DIALOG_IDS_KEY);
+        final int numDialogs = ids.length;
+        mManagedDialogs = new SparseArray<ManagedDialog>(numDialogs);
+        for (int i = 0; i < numDialogs; i++) {
+            final Integer dialogId = ids[i];
+            Bundle dialogState = b.getBundle(savedDialogKeyFor(dialogId));
+            if (dialogState != null) {
+                // Calling onRestoreInstanceState() below will invoke dispatchOnCreate
+                // so tell createDialog() not to do it, otherwise we get an exception
+                final ManagedDialog md = new ManagedDialog();
+                md.mArgs = b.getBundle(savedDialogArgsKeyFor(dialogId));
+                md.mDialog = createDialog(dialogId, dialogState, md.mArgs);
+                if (md.mDialog != null) {
+                    mManagedDialogs.put(dialogId, md);
+                    onPrepareDialog(dialogId, md.mDialog, md.mArgs);
+                    md.mDialog.onRestoreInstanceState(dialogState);
+                }
+            }
+        }
+    }
+
+    private Dialog createDialog(Integer dialogId, Bundle state, Bundle args) {
+        final Dialog dialog = onCreateDialog(dialogId, args);
+        if (dialog == null) {
+            return null;
+        }
+        dialog.dispatchOnCreate(state);
+        return dialog;
+    }
+
+    private static String savedDialogKeyFor(int key) {
+        return SAVED_DIALOG_KEY_PREFIX + key;
+    }
+
+    private static String savedDialogArgsKeyFor(int key) {
+        return SAVED_DIALOG_ARGS_KEY_PREFIX + key;
+    }
+
+    /**
+     * Called when activity start-up is complete (after {@link #onStart}
+     * and {@link #onRestoreInstanceState} have been called).  Applications will
+     * generally not implement this method; it is intended for system
+     * classes to do final initialization after application code has run.
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     *
+     * @param savedInstanceState If the activity is being re-initialized after
+     *     previously being shut down then this Bundle contains the data it most
+     *     recently supplied in {@link #onSaveInstanceState}.  <b><i>Note: Otherwise it is null.</i></b>
+     * @see #onCreate
+     */
+    @CallSuper
+    protected void onPostCreate(@Nullable Bundle savedInstanceState) {
+        if (!isChild()) {
+            mTitleReady = true;
+            onTitleChanged(getTitle(), getTitleColor());
+        }
+
+        mCalled = true;
+
+        notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_START);
+    }
+
+    /**
+     * This is the same as {@link #onPostCreate(Bundle)} but is called for activities
+     * created with the attribute {@link android.R.attr#persistableMode} set to
+     * <code>persistAcrossReboots</code>.
+     *
+     * @param savedInstanceState The data most recently supplied in {@link #onSaveInstanceState}
+     * @param persistentState The data caming from the PersistableBundle first
+     * saved in {@link #onSaveInstanceState(Bundle, PersistableBundle)}.
+     *
+     * @see #onCreate
+     */
+    public void onPostCreate(@Nullable Bundle savedInstanceState,
+            @Nullable PersistableBundle persistentState) {
+        onPostCreate(savedInstanceState);
+    }
+
+    /**
+     * Called after {@link #onCreate} &mdash; or after {@link #onRestart} when
+     * the activity had been stopped, but is now again being displayed to the
+     * user. It will usually be followed by {@link #onResume}. This is a good place to begin
+     * drawing visual elements, running animations, etc.
+     *
+     * <p>You can call {@link #finish} from within this function, in
+     * which case {@link #onStop} will be immediately called after {@link #onStart} without the
+     * lifecycle transitions in-between ({@link #onResume}, {@link #onPause}, etc) executing.
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     *
+     * @see #onCreate
+     * @see #onStop
+     * @see #onResume
+     */
+    @CallSuper
+    protected void onStart() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
+        mCalled = true;
+
+        mFragments.doLoaderStart();
+
+        dispatchActivityStarted();
+
+        if (mAutoFillResetNeeded) {
+            getAutofillManager().onVisibleForAutofill();
+        }
+    }
+
+    /**
+     * Called after {@link #onStop} when the current activity is being
+     * re-displayed to the user (the user has navigated back to it).  It will
+     * be followed by {@link #onStart} and then {@link #onResume}.
+     *
+     * <p>For activities that are using raw {@link Cursor} objects (instead of
+     * creating them through
+     * {@link #managedQuery(android.net.Uri , String[], String, String[], String)},
+     * this is usually the place
+     * where the cursor should be requeried (because you had deactivated it in
+     * {@link #onStop}.
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     *
+     * @see #onStop
+     * @see #onStart
+     * @see #onResume
+     */
+    @CallSuper
+    protected void onRestart() {
+        mCalled = true;
+    }
+
+    /**
+     * Called when an {@link #onResume} is coming up, prior to other pre-resume callbacks
+     * such as {@link #onNewIntent} and {@link #onActivityResult}.  This is primarily intended
+     * to give the activity a hint that its state is no longer saved -- it will generally
+     * be called after {@link #onSaveInstanceState} and prior to the activity being
+     * resumed/started again.
+     *
+     * @deprecated starting with {@link android.os.Build.VERSION_CODES#P} onSaveInstanceState is
+     * called after {@link #onStop}, so this hint isn't accurate anymore: you should consider your
+     * state not saved in between {@code onStart} and {@code onStop} callbacks inclusively.
+     */
+    @Deprecated
+    public void onStateNotSaved() {
+    }
+
+    /**
+     * Called after {@link #onRestoreInstanceState}, {@link #onRestart}, or
+     * {@link #onPause}, for your activity to start interacting with the user. This is an indicator
+     * that the activity became active and ready to receive input. It is on top of an activity stack
+     * and visible to user.
+     *
+     * <p>On platform versions prior to {@link android.os.Build.VERSION_CODES#Q} this is also a good
+     * place to try to open exclusive-access devices or to get access to singleton resources.
+     * Starting  with {@link android.os.Build.VERSION_CODES#Q} there can be multiple resumed
+     * activities in the system simultaneously, so {@link #onTopResumedActivityChanged(boolean)}
+     * should be used for that purpose instead.
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     *
+     * @see #onRestoreInstanceState
+     * @see #onRestart
+     * @see #onPostResume
+     * @see #onPause
+     * @see #onTopResumedActivityChanged(boolean)
+     */
+    @CallSuper
+    protected void onResume() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onResume " + this);
+        dispatchActivityResumed();
+        mActivityTransitionState.onResume(this);
+        enableAutofillCompatibilityIfNeeded();
+        if (mAutoFillResetNeeded) {
+            if (!mAutoFillIgnoreFirstResumePause) {
+                View focus = getCurrentFocus();
+                if (focus != null && focus.canNotifyAutofillEnterExitEvent()) {
+                    // TODO: in Activity killed/recreated case, i.e. SessionLifecycleTest#
+                    // testDatasetVisibleWhileAutofilledAppIsLifecycled: the View's initial
+                    // window visibility after recreation is INVISIBLE in onResume() and next frame
+                    // ViewRootImpl.performTraversals() changes window visibility to VISIBLE.
+                    // So we cannot call View.notifyEnterOrExited() which will do nothing
+                    // when View.isVisibleToUser() is false.
+                    getAutofillManager().notifyViewEntered(focus);
+                }
+            }
+        }
+
+        notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_RESUME);
+
+        mCalled = true;
+    }
+
+    /**
+     * Called when activity resume is complete (after {@link #onResume} has
+     * been called). Applications will generally not implement this method;
+     * it is intended for system classes to do final setup after application
+     * resume code has run.
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     *
+     * @see #onResume
+     */
+    @CallSuper
+    protected void onPostResume() {
+        final Window win = getWindow();
+        if (win != null) win.makeActive();
+        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(true);
+        mCalled = true;
+    }
+
+    /**
+     * Called when activity gets or loses the top resumed position in the system.
+     *
+     * <p>Starting with {@link android.os.Build.VERSION_CODES#Q} multiple activities can be resumed
+     * at the same time in multi-window and multi-display modes. This callback should be used
+     * instead of {@link #onResume()} as an indication that the activity can try to open
+     * exclusive-access devices like camera.</p>
+     *
+     * <p>It will always be delivered after the activity was resumed and before it is paused. In
+     * some cases it might be skipped and activity can go straight from {@link #onResume()} to
+     * {@link #onPause()} without receiving the top resumed state.</p>
+     *
+     * @param isTopResumedActivity {@code true} if it's the topmost resumed activity in the system,
+     *                             {@code false} otherwise. A call with this as {@code true} will
+     *                             always be followed by another one with {@code false}.
+     *
+     * @see #onResume()
+     * @see #onPause()
+     * @see #onWindowFocusChanged(boolean)
+     */
+    public void onTopResumedActivityChanged(boolean isTopResumedActivity) {
+    }
+
+    final void performTopResumedActivityChanged(boolean isTopResumedActivity, String reason) {
+        onTopResumedActivityChanged(isTopResumedActivity);
+
+        writeEventLog(isTopResumedActivity
+                ? LOG_AM_ON_TOP_RESUMED_GAINED_CALLED : LOG_AM_ON_TOP_RESUMED_LOST_CALLED, reason);
+    }
+
+    void setVoiceInteractor(IVoiceInteractor voiceInteractor) {
+        if (mVoiceInteractor != null) {
+            final Request[] requests = mVoiceInteractor.getActiveRequests();
+            if (requests != null) {
+                for (Request activeRequest : mVoiceInteractor.getActiveRequests()) {
+                    activeRequest.cancel();
+                    activeRequest.clear();
+                }
+            }
+        }
+        if (voiceInteractor == null) {
+            mVoiceInteractor = null;
+        } else {
+            mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
+                    Looper.myLooper());
+        }
+    }
+
+    /**
+     * Gets the next autofill ID.
+     *
+     * <p>All IDs will be bigger than {@link View#LAST_APP_AUTOFILL_ID}. All IDs returned
+     * will be unique.
+     *
+     * @return A ID that is unique in the activity
+     *
+     * {@hide}
+     */
+    @Override
+    public int getNextAutofillId() {
+        if (mLastAutofillId == Integer.MAX_VALUE - 1) {
+            mLastAutofillId = View.LAST_APP_AUTOFILL_ID;
+        }
+
+        mLastAutofillId++;
+
+        return mLastAutofillId;
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public AutofillId autofillClientGetNextAutofillId() {
+        return new AutofillId(getNextAutofillId());
+    }
+
+    /**
+     * Check whether this activity is running as part of a voice interaction with the user.
+     * If true, it should perform its interaction with the user through the
+     * {@link VoiceInteractor} returned by {@link #getVoiceInteractor}.
+     */
+    public boolean isVoiceInteraction() {
+        return mVoiceInteractor != null;
+    }
+
+    /**
+     * Like {@link #isVoiceInteraction}, but only returns true if this is also the root
+     * of a voice interaction.  That is, returns true if this activity was directly
+     * started by the voice interaction service as the initiation of a voice interaction.
+     * Otherwise, for example if it was started by another activity while under voice
+     * interaction, returns false.
+     */
+    public boolean isVoiceInteractionRoot() {
+        try {
+            return mVoiceInteractor != null
+                    && ActivityTaskManager.getService().isRootVoiceInteraction(mToken);
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
+    /**
+     * Retrieve the active {@link VoiceInteractor} that the user is going through to
+     * interact with this activity.
+     */
+    public VoiceInteractor getVoiceInteractor() {
+        return mVoiceInteractor;
+    }
+
+    /**
+     * Queries whether the currently enabled voice interaction service supports returning
+     * a voice interactor for use by the activity. This is valid only for the duration of the
+     * activity.
+     *
+     * @return whether the current voice interaction service supports local voice interaction
+     */
+    public boolean isLocalVoiceInteractionSupported() {
+        try {
+            return ActivityTaskManager.getService().supportsLocalVoiceInteraction();
+        } catch (RemoteException re) {
+        }
+        return false;
+    }
+
+    /**
+     * Starts a local voice interaction session. When ready,
+     * {@link #onLocalVoiceInteractionStarted()} is called. You can pass a bundle of private options
+     * to the registered voice interaction service.
+     * @param privateOptions a Bundle of private arguments to the current voice interaction service
+     */
+    public void startLocalVoiceInteraction(Bundle privateOptions) {
+        try {
+            ActivityTaskManager.getService().startLocalVoiceInteraction(mToken, privateOptions);
+        } catch (RemoteException re) {
+        }
+    }
+
+    /**
+     * Callback to indicate that {@link #startLocalVoiceInteraction(Bundle)} has resulted in a
+     * voice interaction session being started. You can now retrieve a voice interactor using
+     * {@link #getVoiceInteractor()}.
+     */
+    public void onLocalVoiceInteractionStarted() {
+    }
+
+    /**
+     * Callback to indicate that the local voice interaction has stopped either
+     * because it was requested through a call to {@link #stopLocalVoiceInteraction()}
+     * or because it was canceled by the user. The previously acquired {@link VoiceInteractor}
+     * is no longer valid after this.
+     */
+    public void onLocalVoiceInteractionStopped() {
+    }
+
+    /**
+     * Request to terminate the current voice interaction that was previously started
+     * using {@link #startLocalVoiceInteraction(Bundle)}. When the interaction is
+     * terminated, {@link #onLocalVoiceInteractionStopped()} will be called.
+     */
+    public void stopLocalVoiceInteraction() {
+        try {
+            ActivityTaskManager.getService().stopLocalVoiceInteraction(mToken);
+        } catch (RemoteException re) {
+        }
+    }
+
+    /**
+     * This is called for activities that set launchMode to "singleTop" in
+     * their package, or if a client used the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP}
+     * flag when calling {@link #startActivity}.  In either case, when the
+     * activity is re-launched while at the top of the activity stack instead
+     * of a new instance of the activity being started, onNewIntent() will be
+     * called on the existing instance with the Intent that was used to
+     * re-launch it.
+     *
+     * <p>An activity can never receive a new intent in the resumed state. You can count on
+     * {@link #onResume} being called after this method, though not necessarily immediately after
+     * the completion this callback. If the activity was resumed, it will be paused and new intent
+     * will be delivered, followed by {@link #onResume}. If the activity wasn't in the resumed
+     * state, then new intent can be delivered immediately, with {@link #onResume()} called
+     * sometime later when activity becomes active again.
+     *
+     * <p>Note that {@link #getIntent} still returns the original Intent.  You
+     * can use {@link #setIntent} to update it to this new Intent.
+     *
+     * @param intent The new intent that was started for the activity.
+     *
+     * @see #getIntent
+     * @see #setIntent
+     * @see #onResume
+     */
+    protected void onNewIntent(Intent intent) {
+    }
+
+    /**
+     * The hook for {@link ActivityThread} to save the state of this activity.
+     *
+     * Calls {@link #onSaveInstanceState(android.os.Bundle)}
+     * and {@link #saveManagedDialogs(android.os.Bundle)}.
+     *
+     * @param outState The bundle to save the state to.
+     */
+    final void performSaveInstanceState(@NonNull Bundle outState) {
+        dispatchActivityPreSaveInstanceState(outState);
+        onSaveInstanceState(outState);
+        saveManagedDialogs(outState);
+        mActivityTransitionState.saveState(outState);
+        storeHasCurrentPermissionRequest(outState);
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState);
+        dispatchActivityPostSaveInstanceState(outState);
+    }
+
+    /**
+     * The hook for {@link ActivityThread} to save the state of this activity.
+     *
+     * Calls {@link #onSaveInstanceState(android.os.Bundle)}
+     * and {@link #saveManagedDialogs(android.os.Bundle)}.
+     *
+     * @param outState The bundle to save the state to.
+     * @param outPersistentState The bundle to save persistent state to.
+     */
+    final void performSaveInstanceState(@NonNull Bundle outState,
+            @NonNull PersistableBundle outPersistentState) {
+        dispatchActivityPreSaveInstanceState(outState);
+        onSaveInstanceState(outState, outPersistentState);
+        saveManagedDialogs(outState);
+        storeHasCurrentPermissionRequest(outState);
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onSaveInstanceState " + this + ": " + outState +
+                ", " + outPersistentState);
+        dispatchActivityPostSaveInstanceState(outState);
+    }
+
+    /**
+     * Called to retrieve per-instance state from an activity before being killed
+     * so that the state can be restored in {@link #onCreate} or
+     * {@link #onRestoreInstanceState} (the {@link Bundle} populated by this method
+     * will be passed to both).
+     *
+     * <p>This method is called before an activity may be killed so that when it
+     * comes back some time in the future it can restore its state.  For example,
+     * if activity B is launched in front of activity A, and at some point activity
+     * A is killed to reclaim resources, activity A will have a chance to save the
+     * current state of its user interface via this method so that when the user
+     * returns to activity A, the state of the user interface can be restored
+     * via {@link #onCreate} or {@link #onRestoreInstanceState}.
+     *
+     * <p>Do not confuse this method with activity lifecycle callbacks such as {@link #onPause},
+     * which is always called when the user no longer actively interacts with an activity, or
+     * {@link #onStop} which is called when activity becomes invisible. One example of when
+     * {@link #onPause} and {@link #onStop} is called and not this method is when a user navigates
+     * back from activity B to activity A: there is no need to call {@link #onSaveInstanceState}
+     * on B because that particular instance will never be restored,
+     * so the system avoids calling it.  An example when {@link #onPause} is called and
+     * not {@link #onSaveInstanceState} is when activity B is launched in front of activity A:
+     * the system may avoid calling {@link #onSaveInstanceState} on activity A if it isn't
+     * killed during the lifetime of B since the state of the user interface of
+     * A will stay intact.
+     *
+     * <p>The default implementation takes care of most of the UI per-instance
+     * state for you by calling {@link android.view.View#onSaveInstanceState()} on each
+     * view in the hierarchy that has an id, and by saving the id of the currently
+     * focused view (all of which is restored by the default implementation of
+     * {@link #onRestoreInstanceState}).  If you override this method to save additional
+     * information not captured by each individual view, you will likely want to
+     * call through to the default implementation, otherwise be prepared to save
+     * all of the state of each view yourself.
+     *
+     * <p>If called, this method will occur after {@link #onStop} for applications
+     * targeting platforms starting with {@link android.os.Build.VERSION_CODES#P}.
+     * For applications targeting earlier platform versions this method will occur
+     * before {@link #onStop} and there are no guarantees about whether it will
+     * occur before or after {@link #onPause}.
+     *
+     * @param outState Bundle in which to place your saved state.
+     *
+     * @see #onCreate
+     * @see #onRestoreInstanceState
+     * @see #onPause
+     */
+    protected void onSaveInstanceState(@NonNull Bundle outState) {
+        outState.putBundle(WINDOW_HIERARCHY_TAG, mWindow.saveHierarchyState());
+
+        outState.putInt(LAST_AUTOFILL_ID, mLastAutofillId);
+        Parcelable p = mFragments.saveAllState();
+        if (p != null) {
+            outState.putParcelable(FRAGMENTS_TAG, p);
+        }
+        if (mAutoFillResetNeeded) {
+            outState.putBoolean(AUTOFILL_RESET_NEEDED, true);
+            getAutofillManager().onSaveInstanceState(outState);
+        }
+        dispatchActivitySaveInstanceState(outState);
+    }
+
+    /**
+     * This is the same as {@link #onSaveInstanceState} but is called for activities
+     * created with the attribute {@link android.R.attr#persistableMode} set to
+     * <code>persistAcrossReboots</code>. The {@link android.os.PersistableBundle} passed
+     * in will be saved and presented in {@link #onCreate(Bundle, PersistableBundle)}
+     * the first time that this activity is restarted following the next device reboot.
+     *
+     * @param outState Bundle in which to place your saved state.
+     * @param outPersistentState State which will be saved across reboots.
+     *
+     * @see #onSaveInstanceState(Bundle)
+     * @see #onCreate
+     * @see #onRestoreInstanceState(Bundle, PersistableBundle)
+     * @see #onPause
+     */
+    public void onSaveInstanceState(@NonNull Bundle outState,
+            @NonNull PersistableBundle outPersistentState) {
+        onSaveInstanceState(outState);
+    }
+
+    /**
+     * Save the state of any managed dialogs.
+     *
+     * @param outState place to store the saved state.
+     */
+    @UnsupportedAppUsage
+    private void saveManagedDialogs(Bundle outState) {
+        if (mManagedDialogs == null) {
+            return;
+        }
+
+        final int numDialogs = mManagedDialogs.size();
+        if (numDialogs == 0) {
+            return;
+        }
+
+        Bundle dialogState = new Bundle();
+
+        int[] ids = new int[mManagedDialogs.size()];
+
+        // save each dialog's bundle, gather the ids
+        for (int i = 0; i < numDialogs; i++) {
+            final int key = mManagedDialogs.keyAt(i);
+            ids[i] = key;
+            final ManagedDialog md = mManagedDialogs.valueAt(i);
+            dialogState.putBundle(savedDialogKeyFor(key), md.mDialog.onSaveInstanceState());
+            if (md.mArgs != null) {
+                dialogState.putBundle(savedDialogArgsKeyFor(key), md.mArgs);
+            }
+        }
+
+        dialogState.putIntArray(SAVED_DIALOG_IDS_KEY, ids);
+        outState.putBundle(SAVED_DIALOGS_TAG, dialogState);
+    }
+
+
+    /**
+     * Called as part of the activity lifecycle when the user no longer actively interacts with the
+     * activity, but it is still visible on screen. The counterpart to {@link #onResume}.
+     *
+     * <p>When activity B is launched in front of activity A, this callback will
+     * be invoked on A.  B will not be created until A's {@link #onPause} returns,
+     * so be sure to not do anything lengthy here.
+     *
+     * <p>This callback is mostly used for saving any persistent state the
+     * activity is editing, to present a "edit in place" model to the user and
+     * making sure nothing is lost if there are not enough resources to start
+     * the new activity without first killing this one.  This is also a good
+     * place to stop things that consume a noticeable amount of CPU in order to
+     * make the switch to the next activity as fast as possible.
+     *
+     * <p>On platform versions prior to {@link android.os.Build.VERSION_CODES#Q} this is also a good
+     * place to try to close exclusive-access devices or to release access to singleton resources.
+     * Starting with {@link android.os.Build.VERSION_CODES#Q} there can be multiple resumed
+     * activities in the system at the same time, so {@link #onTopResumedActivityChanged(boolean)}
+     * should be used for that purpose instead.
+     *
+     * <p>If an activity is launched on top, after receiving this call you will usually receive a
+     * following call to {@link #onStop} (after the next activity has been resumed and displayed
+     * above). However in some cases there will be a direct call back to {@link #onResume} without
+     * going through the stopped state. An activity can also rest in paused state in some cases when
+     * in multi-window mode, still visible to user.
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     *
+     * @see #onResume
+     * @see #onSaveInstanceState
+     * @see #onStop
+     */
+    @CallSuper
+    protected void onPause() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onPause " + this);
+        dispatchActivityPaused();
+        if (mAutoFillResetNeeded) {
+            if (!mAutoFillIgnoreFirstResumePause) {
+                if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill notifyViewExited " + this);
+                View focus = getCurrentFocus();
+                if (focus != null && focus.canNotifyAutofillEnterExitEvent()) {
+                    getAutofillManager().notifyViewExited(focus);
+                }
+            } else {
+                // reset after first pause()
+                if (DEBUG_LIFECYCLE) Slog.v(TAG, "autofill got first pause " + this);
+                mAutoFillIgnoreFirstResumePause = false;
+            }
+        }
+
+        notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_PAUSE);
+        mCalled = true;
+    }
+
+    /**
+     * Called as part of the activity lifecycle when an activity is about to go
+     * into the background as the result of user choice.  For example, when the
+     * user presses the Home key, {@link #onUserLeaveHint} will be called, but
+     * when an incoming phone call causes the in-call Activity to be automatically
+     * brought to the foreground, {@link #onUserLeaveHint} will not be called on
+     * the activity being interrupted.  In cases when it is invoked, this method
+     * is called right before the activity's {@link #onPause} callback.
+     *
+     * <p>This callback and {@link #onUserInteraction} are intended to help
+     * activities manage status bar notifications intelligently; specifically,
+     * for helping activities determine the proper time to cancel a notification.
+     *
+     * @see #onUserInteraction()
+     * @see android.content.Intent#FLAG_ACTIVITY_NO_USER_ACTION
+     */
+    protected void onUserLeaveHint() {
+    }
+
+    /**
+     * @deprecated Method doesn't do anything and will be removed in the future.
+     */
+    @Deprecated
+    public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) {
+        return false;
+    }
+
+    /**
+     * Generate a new description for this activity.  This method is called
+     * before stopping the activity and can, if desired, return some textual
+     * description of its current state to be displayed to the user.
+     *
+     * <p>The default implementation returns null, which will cause you to
+     * inherit the description from the previous activity.  If all activities
+     * return null, generally the label of the top activity will be used as the
+     * description.
+     *
+     * @return A description of what the user is doing.  It should be short and
+     *         sweet (only a few words).
+     *
+     * @see #onSaveInstanceState
+     * @see #onStop
+     */
+    @Nullable
+    public CharSequence onCreateDescription() {
+        return null;
+    }
+
+    /**
+     * This is called when the user is requesting an assist, to build a full
+     * {@link Intent#ACTION_ASSIST} Intent with all of the context of the current
+     * application.  You can override this method to place into the bundle anything
+     * you would like to appear in the {@link Intent#EXTRA_ASSIST_CONTEXT} part
+     * of the assist Intent.
+     *
+     * <p>This function will be called after any global assist callbacks that had
+     * been registered with {@link Application#registerOnProvideAssistDataListener
+     * Application.registerOnProvideAssistDataListener}.
+     */
+    public void onProvideAssistData(Bundle data) {
+    }
+
+    /**
+     * This is called when the user is requesting an assist, to provide references
+     * to content related to the current activity.  Before being called, the
+     * {@code outContent} Intent is filled with the base Intent of the activity (the Intent
+     * returned by {@link #getIntent()}).  The Intent's extras are stripped of any types
+     * that are not valid for {@link PersistableBundle} or non-framework Parcelables, and
+     * the flags {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION} and
+     * {@link Intent#FLAG_GRANT_PERSISTABLE_URI_PERMISSION} are cleared from the Intent.
+     *
+     * <p>Custom implementation may adjust the content intent to better reflect the top-level
+     * context of the activity, and fill in its ClipData with additional content of
+     * interest that the user is currently viewing.  For example, an image gallery application
+     * that has launched in to an activity allowing the user to swipe through pictures should
+     * modify the intent to reference the current image they are looking it; such an
+     * application when showing a list of pictures should add a ClipData that has
+     * references to all of the pictures currently visible on screen.</p>
+     *
+     * @param outContent The assist content to return.
+     */
+    public void onProvideAssistContent(AssistContent outContent) {
+    }
+
+    /**
+     * Returns the list of direct actions supported by the app.
+     *
+     * <p>You should return the list of actions that could be executed in the
+     * current context, which is in the current state of the app. If the actions
+     * that could be executed by the app changes you should report that via
+     * calling {@link VoiceInteractor#notifyDirectActionsChanged()}.
+     *
+     * <p>To get the voice interactor you need to call {@link #getVoiceInteractor()}
+     * which would return non <code>null</code> only if there is an ongoing voice
+     * interaction session. You an also detect when the voice interactor is no
+     * longer valid because the voice interaction session that is backing is finished
+     * by calling {@link VoiceInteractor#registerOnDestroyedCallback(Executor, Runnable)}.
+     *
+     * <p>This method will be called only after {@link #onStart()} is being called and
+     * before {@link #onStop()} is being called.
+     *
+     * <p>You should pass to the callback the currently supported direct actions which
+     * cannot be <code>null</code> or contain <code>null</code> elements.
+     *
+     * <p>You should return the action list as soon as possible to ensure the consumer,
+     * for example the assistant, is as responsive as possible which would improve user
+     * experience of your app.
+     *
+     * @param cancellationSignal A signal to cancel the operation in progress.
+     * @param callback The callback to send the action list. The actions list cannot
+     *     contain <code>null</code> elements. You can call this on any thread.
+     */
+    public void onGetDirectActions(@NonNull CancellationSignal cancellationSignal,
+            @NonNull Consumer<List<DirectAction>> callback) {
+        callback.accept(Collections.emptyList());
+    }
+
+    /**
+     * This is called to perform an action previously defined by the app.
+     * Apps also have access to {@link #getVoiceInteractor()} to follow up on the action.
+     *
+     * @param actionId The ID for the action you previously reported via
+     *     {@link #onGetDirectActions(CancellationSignal, Consumer)}.
+     * @param arguments Any additional arguments provided by the caller that are
+     *     specific to the given action.
+     * @param cancellationSignal A signal to cancel the operation in progress.
+     * @param resultListener The callback to provide the result back to the caller.
+     *     You can call this on any thread. The result bundle is action specific.
+     *
+     * @see #onGetDirectActions(CancellationSignal, Consumer)
+     */
+    public void onPerformDirectAction(@NonNull String actionId,
+            @NonNull Bundle arguments, @NonNull CancellationSignal cancellationSignal,
+            @NonNull Consumer<Bundle> resultListener) { }
+
+    /**
+     * Request the Keyboard Shortcuts screen to show up. This will trigger
+     * {@link #onProvideKeyboardShortcuts} to retrieve the shortcuts for the foreground activity.
+     */
+    public final void requestShowKeyboardShortcuts() {
+        Intent intent = new Intent(Intent.ACTION_SHOW_KEYBOARD_SHORTCUTS);
+        intent.setPackage(KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME);
+        sendBroadcastAsUser(intent, Process.myUserHandle());
+    }
+
+    /**
+     * Dismiss the Keyboard Shortcuts screen.
+     */
+    public final void dismissKeyboardShortcutsHelper() {
+        Intent intent = new Intent(Intent.ACTION_DISMISS_KEYBOARD_SHORTCUTS);
+        intent.setPackage(KEYBOARD_SHORTCUTS_RECEIVER_PKG_NAME);
+        sendBroadcastAsUser(intent, Process.myUserHandle());
+    }
+
+    @Override
+    public void onProvideKeyboardShortcuts(
+            List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+        if (menu == null) {
+          return;
+        }
+        KeyboardShortcutGroup group = null;
+        int menuSize = menu.size();
+        for (int i = 0; i < menuSize; ++i) {
+            final MenuItem item = menu.getItem(i);
+            final CharSequence title = item.getTitle();
+            final char alphaShortcut = item.getAlphabeticShortcut();
+            final int alphaModifiers = item.getAlphabeticModifiers();
+            if (title != null && alphaShortcut != MIN_VALUE) {
+                if (group == null) {
+                    final int resource = mApplication.getApplicationInfo().labelRes;
+                    group = new KeyboardShortcutGroup(resource != 0 ? getString(resource) : null);
+                }
+                group.addItem(new KeyboardShortcutInfo(
+                    title, alphaShortcut, alphaModifiers));
+            }
+        }
+        if (group != null) {
+            data.add(group);
+        }
+    }
+
+    /**
+     * Ask to have the current assistant shown to the user.  This only works if the calling
+     * activity is the current foreground activity.  It is the same as calling
+     * {@link android.service.voice.VoiceInteractionService#showSession
+     * VoiceInteractionService.showSession} and requesting all of the possible context.
+     * The receiver will always see
+     * {@link android.service.voice.VoiceInteractionSession#SHOW_SOURCE_APPLICATION} set.
+     * @return Returns true if the assistant was successfully invoked, else false.  For example
+     * false will be returned if the caller is not the current top activity.
+     */
+    public boolean showAssist(Bundle args) {
+        try {
+            return ActivityTaskManager.getService().showAssistFromActivity(mToken, args);
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
+    /**
+     * Called when you are no longer visible to the user.  You will next
+     * receive either {@link #onRestart}, {@link #onDestroy}, or nothing,
+     * depending on later user activity. This is a good place to stop
+     * refreshing UI, running animations and other visual things.
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     *
+     * @see #onRestart
+     * @see #onResume
+     * @see #onSaveInstanceState
+     * @see #onDestroy
+     */
+    @CallSuper
+    protected void onStop() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStop " + this);
+        if (mActionBar != null) mActionBar.setShowHideAnimationEnabled(false);
+        mActivityTransitionState.onStop();
+        dispatchActivityStopped();
+        mTranslucentCallback = null;
+        mCalled = true;
+
+        if (mAutoFillResetNeeded) {
+            getAutofillManager().onInvisibleForAutofill();
+        }
+
+        if (isFinishing()) {
+            if (mAutoFillResetNeeded) {
+                getAutofillManager().onActivityFinishing();
+            } else if (mIntent != null
+                    && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
+                // Activity was launched when user tapped a link in the Autofill Save UI - since
+                // user launched another activity, the Save UI should not be restored when this
+                // activity is finished.
+                getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_CANCEL,
+                        mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN));
+            }
+        }
+        mEnterAnimationComplete = false;
+    }
+
+    /**
+     * Perform any final cleanup before an activity is destroyed.  This can
+     * happen either because the activity is finishing (someone called
+     * {@link #finish} on it), or because the system is temporarily destroying
+     * this instance of the activity to save space.  You can distinguish
+     * between these two scenarios with the {@link #isFinishing} method.
+     *
+     * <p><em>Note: do not count on this method being called as a place for
+     * saving data! For example, if an activity is editing data in a content
+     * provider, those edits should be committed in either {@link #onPause} or
+     * {@link #onSaveInstanceState}, not here.</em> This method is usually implemented to
+     * free resources like threads that are associated with an activity, so
+     * that a destroyed activity does not leave such things around while the
+     * rest of its application is still running.  There are situations where
+     * the system will simply kill the activity's hosting process without
+     * calling this method (or any others) in it, so it should not be used to
+     * do things that are intended to remain around after the process goes
+     * away.
+     *
+     * <p><em>Derived classes must call through to the super class's
+     * implementation of this method.  If they do not, an exception will be
+     * thrown.</em></p>
+     *
+     * @see #onPause
+     * @see #onStop
+     * @see #finish
+     * @see #isFinishing
+     */
+    @CallSuper
+    protected void onDestroy() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onDestroy " + this);
+        mCalled = true;
+
+        // dismiss any dialogs we are managing.
+        if (mManagedDialogs != null) {
+            final int numDialogs = mManagedDialogs.size();
+            for (int i = 0; i < numDialogs; i++) {
+                final ManagedDialog md = mManagedDialogs.valueAt(i);
+                if (md.mDialog.isShowing()) {
+                    md.mDialog.dismiss();
+                }
+            }
+            mManagedDialogs = null;
+        }
+
+        // close any cursors we are managing.
+        synchronized (mManagedCursors) {
+            int numCursors = mManagedCursors.size();
+            for (int i = 0; i < numCursors; i++) {
+                ManagedCursor c = mManagedCursors.get(i);
+                if (c != null) {
+                    c.mCursor.close();
+                }
+            }
+            mManagedCursors.clear();
+        }
+
+        // Close any open search dialog
+        if (mSearchManager != null) {
+            mSearchManager.stopSearch();
+        }
+
+        if (mActionBar != null) {
+            mActionBar.onDestroy();
+        }
+
+        dispatchActivityDestroyed();
+
+        notifyContentCaptureManagerIfNeeded(CONTENT_CAPTURE_STOP);
+    }
+
+    /**
+     * Report to the system that your app is now fully drawn, purely for diagnostic
+     * purposes (calling it does not impact the visible behavior of the activity).
+     * This is only used to help instrument application launch times, so that the
+     * app can report when it is fully in a usable state; without this, the only thing
+     * the system itself can determine is the point at which the activity's window
+     * is <em>first</em> drawn and displayed.  To participate in app launch time
+     * measurement, you should always call this method after first launch (when
+     * {@link #onCreate(android.os.Bundle)} is called), at the point where you have
+     * entirely drawn your UI and populated with all of the significant data.  You
+     * can safely call this method any time after first launch as well, in which case
+     * it will simply be ignored.
+     */
+    public void reportFullyDrawn() {
+        if (mDoReportFullyDrawn) {
+            mDoReportFullyDrawn = false;
+            try {
+                ActivityTaskManager.getService().reportActivityFullyDrawn(
+                        mToken, mRestoredFromBundle);
+                VMRuntime.getRuntime().notifyStartupCompleted();
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /**
+     * Called by the system when the activity changes from fullscreen mode to multi-window mode and
+     * visa-versa. This method provides the same configuration that will be sent in the following
+     * {@link #onConfigurationChanged(Configuration)} call after the activity enters this mode.
+     *
+     * @see android.R.attr#resizeableActivity
+     *
+     * @param isInMultiWindowMode True if the activity is in multi-window mode.
+     * @param newConfig The new configuration of the activity with the state
+     *                  {@param isInMultiWindowMode}.
+     */
+    public void onMultiWindowModeChanged(boolean isInMultiWindowMode, Configuration newConfig) {
+        // Left deliberately empty. There should be no side effects if a direct
+        // subclass of Activity does not call super.
+        onMultiWindowModeChanged(isInMultiWindowMode);
+    }
+
+    /**
+     * Called by the system when the activity changes from fullscreen mode to multi-window mode and
+     * visa-versa.
+     *
+     * @see android.R.attr#resizeableActivity
+     *
+     * @param isInMultiWindowMode True if the activity is in multi-window mode.
+     *
+     * @deprecated Use {@link #onMultiWindowModeChanged(boolean, Configuration)} instead.
+     */
+    @Deprecated
+    public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+        // Left deliberately empty. There should be no side effects if a direct
+        // subclass of Activity does not call super.
+    }
+
+    /**
+     * Returns true if the activity is currently in multi-window mode.
+     * @see android.R.attr#resizeableActivity
+     *
+     * @return True if the activity is in multi-window mode.
+     */
+    public boolean isInMultiWindowMode() {
+        try {
+            return ActivityTaskManager.getService().isInMultiWindowMode(mToken);
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
+    /**
+     * Called by the system when the activity changes to and from picture-in-picture mode. This
+     * method provides the same configuration that will be sent in the following
+     * {@link #onConfigurationChanged(Configuration)} call after the activity enters this mode.
+     *
+     * @see android.R.attr#supportsPictureInPicture
+     *
+     * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
+     * @param newConfig The new configuration of the activity with the state
+     *                  {@param isInPictureInPictureMode}.
+     */
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode,
+            Configuration newConfig) {
+        // Left deliberately empty. There should be no side effects if a direct
+        // subclass of Activity does not call super.
+        onPictureInPictureModeChanged(isInPictureInPictureMode);
+    }
+
+    /**
+     * Called by the system when the activity changes to and from picture-in-picture mode.
+     *
+     * @see android.R.attr#supportsPictureInPicture
+     *
+     * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
+     *
+     * @deprecated Use {@link #onPictureInPictureModeChanged(boolean, Configuration)} instead.
+     */
+    @Deprecated
+    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+        // Left deliberately empty. There should be no side effects if a direct
+        // subclass of Activity does not call super.
+    }
+
+    /**
+     * Returns true if the activity is currently in picture-in-picture mode.
+     * @see android.R.attr#supportsPictureInPicture
+     *
+     * @return True if the activity is in picture-in-picture mode.
+     */
+    public boolean isInPictureInPictureMode() {
+        try {
+            return ActivityTaskManager.getService().isInPictureInPictureMode(mToken);
+        } catch (RemoteException e) {
+        }
+        return false;
+    }
+
+    /**
+     * Puts the activity in picture-in-picture mode if possible in the current system state. Any
+     * prior calls to {@link #setPictureInPictureParams(PictureInPictureParams)} will still apply
+     * when entering picture-in-picture through this call.
+     *
+     * @see #enterPictureInPictureMode(PictureInPictureParams)
+     * @see android.R.attr#supportsPictureInPicture
+     */
+    @Deprecated
+    public void enterPictureInPictureMode() {
+        enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+    }
+
+    /** @removed */
+    @Deprecated
+    public boolean enterPictureInPictureMode(@NonNull PictureInPictureArgs args) {
+        return enterPictureInPictureMode(PictureInPictureArgs.convert(args));
+    }
+
+    /**
+     * Puts the activity in picture-in-picture mode if possible in the current system state. The
+     * set parameters in {@param params} will be combined with the parameters from prior calls to
+     * {@link #setPictureInPictureParams(PictureInPictureParams)}.
+     *
+     * The system may disallow entering picture-in-picture in various cases, including when the
+     * activity is not visible, if the screen is locked or if the user has an activity pinned.
+     *
+     * @see android.R.attr#supportsPictureInPicture
+     * @see PictureInPictureParams
+     *
+     * @param params non-null parameters to be combined with previously set parameters when entering
+     * picture-in-picture.
+     *
+     * @return true if the system successfully put this activity into picture-in-picture mode or was
+     * already in picture-in-picture mode (see {@link #isInPictureInPictureMode()}). If the device
+     * does not support picture-in-picture, return false.
+     */
+    public boolean enterPictureInPictureMode(@NonNull PictureInPictureParams params) {
+        try {
+            if (!deviceSupportsPictureInPictureMode()) {
+                return false;
+            }
+            if (params == null) {
+                throw new IllegalArgumentException("Expected non-null picture-in-picture params");
+            }
+            if (!mCanEnterPictureInPicture) {
+                throw new IllegalStateException("Activity must be resumed to enter"
+                        + " picture-in-picture");
+            }
+            return ActivityTaskManager.getService().enterPictureInPictureMode(mToken, params);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /** @removed */
+    @Deprecated
+    public void setPictureInPictureArgs(@NonNull PictureInPictureArgs args) {
+        setPictureInPictureParams(PictureInPictureArgs.convert(args));
+    }
+
+    /**
+     * Updates the properties of the picture-in-picture activity, or sets it to be used later when
+     * {@link #enterPictureInPictureMode()} is called.
+     *
+     * @param params the new parameters for the picture-in-picture.
+     */
+    public void setPictureInPictureParams(@NonNull PictureInPictureParams params) {
+        try {
+            if (!deviceSupportsPictureInPictureMode()) {
+                return;
+            }
+            if (params == null) {
+                throw new IllegalArgumentException("Expected non-null picture-in-picture params");
+            }
+            ActivityTaskManager.getService().setPictureInPictureParams(mToken, params);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Return the number of actions that will be displayed in the picture-in-picture UI when the
+     * user interacts with the activity currently in picture-in-picture mode. This number may change
+     * if the global configuration changes (ie. if the device is plugged into an external display),
+     * but will always be larger than three.
+     */
+    public int getMaxNumPictureInPictureActions() {
+        try {
+            return ActivityTaskManager.getService().getMaxNumPictureInPictureActions(mToken);
+        } catch (RemoteException e) {
+            return 0;
+        }
+    }
+
+    /**
+     * @return Whether this device supports picture-in-picture.
+     */
+    private boolean deviceSupportsPictureInPictureMode() {
+        return getPackageManager().hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE);
+    }
+
+    void dispatchMovedToDisplay(int displayId, Configuration config) {
+        updateDisplay(displayId);
+        onMovedToDisplay(displayId, config);
+    }
+
+    /**
+     * Called by the system when the activity is moved from one display to another without
+     * recreation. This means that this activity is declared to handle all changes to configuration
+     * that happened when it was switched to another display, so it wasn't destroyed and created
+     * again.
+     *
+     * <p>This call will be followed by {@link #onConfigurationChanged(Configuration)} if the
+     * applied configuration actually changed. It is up to app developer to choose whether to handle
+     * the change in this method or in the following {@link #onConfigurationChanged(Configuration)}
+     * call.
+     *
+     * <p>Use this callback to track changes to the displays if some activity functionality relies
+     * on an association with some display properties.
+     *
+     * @param displayId The id of the display to which activity was moved.
+     * @param config Configuration of the activity resources on new display after move.
+     *
+     * @see #onConfigurationChanged(Configuration)
+     * @see View#onMovedToDisplay(int, Configuration)
+     * @hide
+     */
+    @TestApi
+    public void onMovedToDisplay(int displayId, Configuration config) {
+    }
+
+    /**
+     * Called by the system when the device configuration changes while your
+     * activity is running.  Note that this will <em>only</em> be called if
+     * you have selected configurations you would like to handle with the
+     * {@link android.R.attr#configChanges} attribute in your manifest.  If
+     * any configuration change occurs that is not selected to be reported
+     * by that attribute, then instead of reporting it the system will stop
+     * and restart the activity (to have it launched with the new
+     * configuration).
+     *
+     * <p>At the time that this function has been called, your Resources
+     * object will have been updated to return resource values matching the
+     * new configuration.
+     *
+     * @param newConfig The new device configuration.
+     */
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onConfigurationChanged " + this + ": " + newConfig);
+        mCalled = true;
+
+        mFragments.dispatchConfigurationChanged(newConfig);
+
+        if (mWindow != null) {
+            // Pass the configuration changed event to the window
+            mWindow.onConfigurationChanged(newConfig);
+        }
+
+        if (mActionBar != null) {
+            // Do this last; the action bar will need to access
+            // view changes from above.
+            mActionBar.onConfigurationChanged(newConfig);
+        }
+    }
+
+    /**
+     * If this activity is being destroyed because it can not handle a
+     * configuration parameter being changed (and thus its
+     * {@link #onConfigurationChanged(Configuration)} method is
+     * <em>not</em> being called), then you can use this method to discover
+     * the set of changes that have occurred while in the process of being
+     * destroyed.  Note that there is no guarantee that these will be
+     * accurate (other changes could have happened at any time), so you should
+     * only use this as an optimization hint.
+     *
+     * @return Returns a bit field of the configuration parameters that are
+     * changing, as defined by the {@link android.content.res.Configuration}
+     * class.
+     */
+    public int getChangingConfigurations() {
+        return mConfigChangeFlags;
+    }
+
+    /**
+     * Retrieve the non-configuration instance data that was previously
+     * returned by {@link #onRetainNonConfigurationInstance()}.  This will
+     * be available from the initial {@link #onCreate} and
+     * {@link #onStart} calls to the new instance, allowing you to extract
+     * any useful dynamic state from the previous instance.
+     *
+     * <p>Note that the data you retrieve here should <em>only</em> be used
+     * as an optimization for handling configuration changes.  You should always
+     * be able to handle getting a null pointer back, and an activity must
+     * still be able to restore itself to its previous state (through the
+     * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
+     * function returns null.
+     *
+     * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API
+     * {@link Fragment#setRetainInstance(boolean)} instead; this is also
+     * available on older platforms through the Android support libraries.
+     *
+     * @return the object previously returned by {@link #onRetainNonConfigurationInstance()}
+     */
+    @Nullable
+    public Object getLastNonConfigurationInstance() {
+        return mLastNonConfigurationInstances != null
+                ? mLastNonConfigurationInstances.activity : null;
+    }
+
+    /**
+     * Called by the system, as part of destroying an
+     * activity due to a configuration change, when it is known that a new
+     * instance will immediately be created for the new configuration.  You
+     * can return any object you like here, including the activity instance
+     * itself, which can later be retrieved by calling
+     * {@link #getLastNonConfigurationInstance()} in the new activity
+     * instance.
+     *
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using a {@link Fragment} with
+     * {@link Fragment#setRetainInstance(boolean)
+     * Fragment.setRetainInstance(boolean}.</em>
+     *
+     * <p>This function is called purely as an optimization, and you must
+     * not rely on it being called.  When it is called, a number of guarantees
+     * will be made to help optimize configuration switching:
+     * <ul>
+     * <li> The function will be called between {@link #onStop} and
+     * {@link #onDestroy}.
+     * <li> A new instance of the activity will <em>always</em> be immediately
+     * created after this one's {@link #onDestroy()} is called.  In particular,
+     * <em>no</em> messages will be dispatched during this time (when the returned
+     * object does not have an activity to be associated with).
+     * <li> The object you return here will <em>always</em> be available from
+     * the {@link #getLastNonConfigurationInstance()} method of the following
+     * activity instance as described there.
+     * </ul>
+     *
+     * <p>These guarantees are designed so that an activity can use this API
+     * to propagate extensive state from the old to new activity instance, from
+     * loaded bitmaps, to network connections, to evenly actively running
+     * threads.  Note that you should <em>not</em> propagate any data that
+     * may change based on the configuration, including any data loaded from
+     * resources such as strings, layouts, or drawables.
+     *
+     * <p>The guarantee of no message handling during the switch to the next
+     * activity simplifies use with active objects.  For example if your retained
+     * state is an {@link android.os.AsyncTask} you are guaranteed that its
+     * call back functions (like {@link android.os.AsyncTask#onPostExecute}) will
+     * not be called from the call here until you execute the next instance's
+     * {@link #onCreate(Bundle)}.  (Note however that there is of course no such
+     * guarantee for {@link android.os.AsyncTask#doInBackground} since that is
+     * running in a separate thread.)
+     *
+     * <p><strong>Note:</strong> For most cases you should use the {@link Fragment} API
+     * {@link Fragment#setRetainInstance(boolean)} instead; this is also
+     * available on older platforms through the Android support libraries.
+     *
+     * @return any Object holding the desired state to propagate to the
+     *         next activity instance
+     */
+    public Object onRetainNonConfigurationInstance() {
+        return null;
+    }
+
+    /**
+     * Retrieve the non-configuration instance data that was previously
+     * returned by {@link #onRetainNonConfigurationChildInstances()}.  This will
+     * be available from the initial {@link #onCreate} and
+     * {@link #onStart} calls to the new instance, allowing you to extract
+     * any useful dynamic state from the previous instance.
+     *
+     * <p>Note that the data you retrieve here should <em>only</em> be used
+     * as an optimization for handling configuration changes.  You should always
+     * be able to handle getting a null pointer back, and an activity must
+     * still be able to restore itself to its previous state (through the
+     * normal {@link #onSaveInstanceState(Bundle)} mechanism) even if this
+     * function returns null.
+     *
+     * @return Returns the object previously returned by
+     * {@link #onRetainNonConfigurationChildInstances()}
+     */
+    @Nullable
+    HashMap<String, Object> getLastNonConfigurationChildInstances() {
+        return mLastNonConfigurationInstances != null
+                ? mLastNonConfigurationInstances.children : null;
+    }
+
+    /**
+     * This method is similar to {@link #onRetainNonConfigurationInstance()} except that
+     * it should return either a mapping from  child activity id strings to arbitrary objects,
+     * or null.  This method is intended to be used by Activity framework subclasses that control a
+     * set of child activities, such as ActivityGroup.  The same guarantees and restrictions apply
+     * as for {@link #onRetainNonConfigurationInstance()}.  The default implementation returns null.
+     */
+    @Nullable
+    HashMap<String,Object> onRetainNonConfigurationChildInstances() {
+        return null;
+    }
+
+    NonConfigurationInstances retainNonConfigurationInstances() {
+        Object activity = onRetainNonConfigurationInstance();
+        HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
+        FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
+
+        // We're already stopped but we've been asked to retain.
+        // Our fragments are taken care of but we need to mark the loaders for retention.
+        // In order to do this correctly we need to restart the loaders first before
+        // handing them off to the next activity.
+        mFragments.doLoaderStart();
+        mFragments.doLoaderStop(true);
+        ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
+
+        if (activity == null && children == null && fragments == null && loaders == null
+                && mVoiceInteractor == null) {
+            return null;
+        }
+
+        NonConfigurationInstances nci = new NonConfigurationInstances();
+        nci.activity = activity;
+        nci.children = children;
+        nci.fragments = fragments;
+        nci.loaders = loaders;
+        if (mVoiceInteractor != null) {
+            mVoiceInteractor.retainInstance();
+            nci.voiceInteractor = mVoiceInteractor;
+        }
+        return nci;
+    }
+
+    public void onLowMemory() {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onLowMemory " + this);
+        mCalled = true;
+        mFragments.dispatchLowMemory();
+    }
+
+    public void onTrimMemory(int level) {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG, "onTrimMemory " + this + ": " + level);
+        mCalled = true;
+        mFragments.dispatchTrimMemory(level);
+    }
+
+    /**
+     * Return the FragmentManager for interacting with fragments associated
+     * with this activity.
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()}
+     */
+    @Deprecated
+    public FragmentManager getFragmentManager() {
+        return mFragments.getFragmentManager();
+    }
+
+    /**
+     * Called when a Fragment is being attached to this activity, immediately
+     * after the call to its {@link Fragment#onAttach Fragment.onAttach()}
+     * method and before {@link Fragment#onCreate Fragment.onCreate()}.
+     *
+     * @deprecated Use {@link
+     * android.support.v4.app.FragmentActivity#onAttachFragment(android.support.v4.app.Fragment)}
+     */
+    @Deprecated
+    public void onAttachFragment(Fragment fragment) {
+    }
+
+    /**
+     * Wrapper around
+     * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
+     * that gives the resulting {@link Cursor} to call
+     * {@link #startManagingCursor} so that the activity will manage its
+     * lifecycle for you.
+     *
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using {@link LoaderManager} instead, available
+     * via {@link #getLoaderManager()}.</em>
+     *
+     * <p><strong>Warning:</strong> Do not call {@link Cursor#close()} on a cursor obtained using
+     * this method, because the activity will do that for you at the appropriate time. However, if
+     * you call {@link #stopManagingCursor} on a cursor from a managed query, the system <em>will
+     * not</em> automatically close the cursor and, in that case, you must call
+     * {@link Cursor#close()}.</p>
+     *
+     * @param uri The URI of the content provider to query.
+     * @param projection List of columns to return.
+     * @param selection SQL WHERE clause.
+     * @param sortOrder SQL ORDER BY clause.
+     *
+     * @return The Cursor that was returned by query().
+     *
+     * @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
+     * @see #startManagingCursor
+     * @hide
+     *
+     * @deprecated Use {@link CursorLoader} instead.
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public final Cursor managedQuery(Uri uri, String[] projection, String selection,
+            String sortOrder) {
+        Cursor c = getContentResolver().query(uri, projection, selection, null, sortOrder);
+        if (c != null) {
+            startManagingCursor(c);
+        }
+        return c;
+    }
+
+    /**
+     * Wrapper around
+     * {@link ContentResolver#query(android.net.Uri , String[], String, String[], String)}
+     * that gives the resulting {@link Cursor} to call
+     * {@link #startManagingCursor} so that the activity will manage its
+     * lifecycle for you.
+     *
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using {@link LoaderManager} instead, available
+     * via {@link #getLoaderManager()}.</em>
+     *
+     * <p><strong>Warning:</strong> Do not call {@link Cursor#close()} on a cursor obtained using
+     * this method, because the activity will do that for you at the appropriate time. However, if
+     * you call {@link #stopManagingCursor} on a cursor from a managed query, the system <em>will
+     * not</em> automatically close the cursor and, in that case, you must call
+     * {@link Cursor#close()}.</p>
+     *
+     * @param uri The URI of the content provider to query.
+     * @param projection List of columns to return.
+     * @param selection SQL WHERE clause.
+     * @param selectionArgs The arguments to selection, if any ?s are pesent
+     * @param sortOrder SQL ORDER BY clause.
+     *
+     * @return The Cursor that was returned by query().
+     *
+     * @see ContentResolver#query(android.net.Uri , String[], String, String[], String)
+     * @see #startManagingCursor
+     *
+     * @deprecated Use {@link CursorLoader} instead.
+     */
+    @Deprecated
+    public final Cursor managedQuery(Uri uri, String[] projection, String selection,
+            String[] selectionArgs, String sortOrder) {
+        Cursor c = getContentResolver().query(uri, projection, selection, selectionArgs, sortOrder);
+        if (c != null) {
+            startManagingCursor(c);
+        }
+        return c;
+    }
+
+    /**
+     * This method allows the activity to take care of managing the given
+     * {@link Cursor}'s lifecycle for you based on the activity's lifecycle.
+     * That is, when the activity is stopped it will automatically call
+     * {@link Cursor#deactivate} on the given Cursor, and when it is later restarted
+     * it will call {@link Cursor#requery} for you.  When the activity is
+     * destroyed, all managed Cursors will be closed automatically.
+     *
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using {@link LoaderManager} instead, available
+     * via {@link #getLoaderManager()}.</em>
+     *
+     * <p><strong>Warning:</strong> Do not call {@link Cursor#close()} on cursor obtained from
+     * {@link #managedQuery}, because the activity will do that for you at the appropriate time.
+     * However, if you call {@link #stopManagingCursor} on a cursor from a managed query, the system
+     * <em>will not</em> automatically close the cursor and, in that case, you must call
+     * {@link Cursor#close()}.</p>
+     *
+     * @param c The Cursor to be managed.
+     *
+     * @see #managedQuery(android.net.Uri , String[], String, String[], String)
+     * @see #stopManagingCursor
+     *
+     * @deprecated Use the new {@link android.content.CursorLoader} class with
+     * {@link LoaderManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     */
+    @Deprecated
+    public void startManagingCursor(Cursor c) {
+        synchronized (mManagedCursors) {
+            mManagedCursors.add(new ManagedCursor(c));
+        }
+    }
+
+    /**
+     * Given a Cursor that was previously given to
+     * {@link #startManagingCursor}, stop the activity's management of that
+     * cursor.
+     *
+     * <p><strong>Warning:</strong> After calling this method on a cursor from a managed query,
+     * the system <em>will not</em> automatically close the cursor and you must call
+     * {@link Cursor#close()}.</p>
+     *
+     * @param c The Cursor that was being managed.
+     *
+     * @see #startManagingCursor
+     *
+     * @deprecated Use the new {@link android.content.CursorLoader} class with
+     * {@link LoaderManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     */
+    @Deprecated
+    public void stopManagingCursor(Cursor c) {
+        synchronized (mManagedCursors) {
+            final int N = mManagedCursors.size();
+            for (int i=0; i<N; i++) {
+                ManagedCursor mc = mManagedCursors.get(i);
+                if (mc.mCursor == c) {
+                    mManagedCursors.remove(i);
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}
+     * this is a no-op.
+     * @hide
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    public void setPersistent(boolean isPersistent) {
+    }
+
+    /**
+     * Finds a view that was identified by the {@code android:id} XML attribute
+     * that was processed in {@link #onCreate}.
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID if found, or {@code null} otherwise
+     * @see View#findViewById(int)
+     * @see Activity#requireViewById(int)
+     */
+    @Nullable
+    public <T extends View> T findViewById(@IdRes int id) {
+        return getWindow().findViewById(id);
+    }
+
+    /**
+     * Finds a view that was  identified by the {@code android:id} XML attribute that was processed
+     * in {@link #onCreate}, or throws an IllegalArgumentException if the ID is invalid, or there is
+     * no matching view in the hierarchy.
+     * <p>
+     * <strong>Note:</strong> In most cases -- depending on compiler support --
+     * the resulting view is automatically cast to the target class type. If
+     * the target class type is unconstrained, an explicit cast may be
+     * necessary.
+     *
+     * @param id the ID to search for
+     * @return a view with given ID
+     * @see View#requireViewById(int)
+     * @see Activity#findViewById(int)
+     */
+    @NonNull
+    public final <T extends View> T requireViewById(@IdRes int id) {
+        T view = findViewById(id);
+        if (view == null) {
+            throw new IllegalArgumentException("ID does not reference a View inside this Activity");
+        }
+        return view;
+    }
+
+    /**
+     * Retrieve a reference to this activity's ActionBar.
+     *
+     * @return The Activity's ActionBar, or null if it does not have one.
+     */
+    @Nullable
+    public ActionBar getActionBar() {
+        initWindowDecorActionBar();
+        return mActionBar;
+    }
+
+    /**
+     * Set a {@link android.widget.Toolbar Toolbar} to act as the {@link ActionBar} for this
+     * Activity window.
+     *
+     * <p>When set to a non-null value the {@link #getActionBar()} method will return
+     * an {@link ActionBar} object that can be used to control the given toolbar as if it were
+     * a traditional window decor action bar. The toolbar's menu will be populated with the
+     * Activity's options menu and the navigation button will be wired through the standard
+     * {@link android.R.id#home home} menu select action.</p>
+     *
+     * <p>In order to use a Toolbar within the Activity's window content the application
+     * must not request the window feature {@link Window#FEATURE_ACTION_BAR FEATURE_ACTION_BAR}.</p>
+     *
+     * @param toolbar Toolbar to set as the Activity's action bar, or {@code null} to clear it
+     */
+    public void setActionBar(@Nullable Toolbar toolbar) {
+        final ActionBar ab = getActionBar();
+        if (ab instanceof WindowDecorActionBar) {
+            throw new IllegalStateException("This Activity already has an action bar supplied " +
+                    "by the window decor. Do not request Window.FEATURE_ACTION_BAR and set " +
+                    "android:windowActionBar to false in your theme to use a Toolbar instead.");
+        }
+
+        // If we reach here then we're setting a new action bar
+        // First clear out the MenuInflater to make sure that it is valid for the new Action Bar
+        mMenuInflater = null;
+
+        // If we have an action bar currently, destroy it
+        if (ab != null) {
+            ab.onDestroy();
+        }
+
+        if (toolbar != null) {
+            final ToolbarActionBar tbab = new ToolbarActionBar(toolbar, getTitle(), this);
+            mActionBar = tbab;
+            mWindow.setCallback(tbab.getWrappedWindowCallback());
+        } else {
+            mActionBar = null;
+            // Re-set the original window callback since we may have already set a Toolbar wrapper
+            mWindow.setCallback(this);
+        }
+
+        invalidateOptionsMenu();
+    }
+
+    /**
+     * Creates a new ActionBar, locates the inflated ActionBarView,
+     * initializes the ActionBar with the view, and sets mActionBar.
+     */
+    private void initWindowDecorActionBar() {
+        Window window = getWindow();
+
+        // Initializing the window decor can change window feature flags.
+        // Make sure that we have the correct set before performing the test below.
+        window.getDecorView();
+
+        if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
+            return;
+        }
+
+        mActionBar = new WindowDecorActionBar(this);
+        mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
+
+        mWindow.setDefaultIcon(mActivityInfo.getIconResource());
+        mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
+    }
+
+    /**
+     * Set the activity content from a layout resource.  The resource will be
+     * inflated, adding all top-level views to the activity.
+     *
+     * @param layoutResID Resource ID to be inflated.
+     *
+     * @see #setContentView(android.view.View)
+     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
+     */
+    public void setContentView(@LayoutRes int layoutResID) {
+        getWindow().setContentView(layoutResID);
+        initWindowDecorActionBar();
+    }
+
+    /**
+     * Set the activity content to an explicit view.  This view is placed
+     * directly into the activity's view hierarchy.  It can itself be a complex
+     * view hierarchy.  When calling this method, the layout parameters of the
+     * specified view are ignored.  Both the width and the height of the view are
+     * set by default to {@link ViewGroup.LayoutParams#MATCH_PARENT}. To use
+     * your own layout parameters, invoke
+     * {@link #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}
+     * instead.
+     *
+     * @param view The desired content to display.
+     *
+     * @see #setContentView(int)
+     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
+     */
+    public void setContentView(View view) {
+        getWindow().setContentView(view);
+        initWindowDecorActionBar();
+    }
+
+    /**
+     * Set the activity content to an explicit view.  This view is placed
+     * directly into the activity's view hierarchy.  It can itself be a complex
+     * view hierarchy.
+     *
+     * @param view The desired content to display.
+     * @param params Layout parameters for the view.
+     *
+     * @see #setContentView(android.view.View)
+     * @see #setContentView(int)
+     */
+    public void setContentView(View view, ViewGroup.LayoutParams params) {
+        getWindow().setContentView(view, params);
+        initWindowDecorActionBar();
+    }
+
+    /**
+     * Add an additional content view to the activity.  Added after any existing
+     * ones in the activity -- existing views are NOT removed.
+     *
+     * @param view The desired content to display.
+     * @param params Layout parameters for the view.
+     */
+    public void addContentView(View view, ViewGroup.LayoutParams params) {
+        getWindow().addContentView(view, params);
+        initWindowDecorActionBar();
+    }
+
+    /**
+     * Retrieve the {@link TransitionManager} responsible for default transitions in this window.
+     * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * <p>This method will return non-null after content has been initialized (e.g. by using
+     * {@link #setContentView}) if {@link Window#FEATURE_CONTENT_TRANSITIONS} has been granted.</p>
+     *
+     * @return This window's content TransitionManager or null if none is set.
+     */
+    public TransitionManager getContentTransitionManager() {
+        return getWindow().getTransitionManager();
+    }
+
+    /**
+     * Set the {@link TransitionManager} to use for default transitions in this window.
+     * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * @param tm The TransitionManager to use for scene changes.
+     */
+    public void setContentTransitionManager(TransitionManager tm) {
+        getWindow().setTransitionManager(tm);
+    }
+
+    /**
+     * Retrieve the {@link Scene} representing this window's current content.
+     * Requires {@link Window#FEATURE_CONTENT_TRANSITIONS}.
+     *
+     * <p>This method will return null if the current content is not represented by a Scene.</p>
+     *
+     * @return Current Scene being shown or null
+     */
+    public Scene getContentScene() {
+        return getWindow().getContentScene();
+    }
+
+    /**
+     * Sets whether this activity is finished when touched outside its window's
+     * bounds.
+     */
+    public void setFinishOnTouchOutside(boolean finish) {
+        mWindow.setCloseOnTouchOutside(finish);
+    }
+
+    /** @hide */
+    @IntDef(prefix = { "DEFAULT_KEYS_" }, value = {
+            DEFAULT_KEYS_DISABLE,
+            DEFAULT_KEYS_DIALER,
+            DEFAULT_KEYS_SHORTCUT,
+            DEFAULT_KEYS_SEARCH_LOCAL,
+            DEFAULT_KEYS_SEARCH_GLOBAL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface DefaultKeyMode {}
+
+    /**
+     * Use with {@link #setDefaultKeyMode} to turn off default handling of
+     * keys.
+     *
+     * @see #setDefaultKeyMode
+     */
+    static public final int DEFAULT_KEYS_DISABLE = 0;
+    /**
+     * Use with {@link #setDefaultKeyMode} to launch the dialer during default
+     * key handling.
+     *
+     * @see #setDefaultKeyMode
+     */
+    static public final int DEFAULT_KEYS_DIALER = 1;
+    /**
+     * Use with {@link #setDefaultKeyMode} to execute a menu shortcut in
+     * default key handling.
+     *
+     * <p>That is, the user does not need to hold down the menu key to execute menu shortcuts.
+     *
+     * @see #setDefaultKeyMode
+     */
+    static public final int DEFAULT_KEYS_SHORTCUT = 2;
+    /**
+     * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes
+     * will start an application-defined search.  (If the application or activity does not
+     * actually define a search, the keys will be ignored.)
+     *
+     * <p>See {@link android.app.SearchManager android.app.SearchManager} for more details.
+     *
+     * @see #setDefaultKeyMode
+     */
+    static public final int DEFAULT_KEYS_SEARCH_LOCAL = 3;
+
+    /**
+     * Use with {@link #setDefaultKeyMode} to specify that unhandled keystrokes
+     * will start a global search (typically web search, but some platforms may define alternate
+     * methods for global search)
+     *
+     * <p>See {@link android.app.SearchManager android.app.SearchManager} for more details.
+     *
+     * @see #setDefaultKeyMode
+     */
+    static public final int DEFAULT_KEYS_SEARCH_GLOBAL = 4;
+
+    /**
+     * Select the default key handling for this activity.  This controls what
+     * will happen to key events that are not otherwise handled.  The default
+     * mode ({@link #DEFAULT_KEYS_DISABLE}) will simply drop them on the
+     * floor. Other modes allow you to launch the dialer
+     * ({@link #DEFAULT_KEYS_DIALER}), execute a shortcut in your options
+     * menu without requiring the menu key be held down
+     * ({@link #DEFAULT_KEYS_SHORTCUT}), or launch a search ({@link #DEFAULT_KEYS_SEARCH_LOCAL}
+     * and {@link #DEFAULT_KEYS_SEARCH_GLOBAL}).
+     *
+     * <p>Note that the mode selected here does not impact the default
+     * handling of system keys, such as the "back" and "menu" keys, and your
+     * activity and its views always get a first chance to receive and handle
+     * all application keys.
+     *
+     * @param mode The desired default key mode constant.
+     *
+     * @see #onKeyDown
+     */
+    public final void setDefaultKeyMode(@DefaultKeyMode int mode) {
+        mDefaultKeyMode = mode;
+
+        // Some modes use a SpannableStringBuilder to track & dispatch input events
+        // This list must remain in sync with the switch in onKeyDown()
+        switch (mode) {
+        case DEFAULT_KEYS_DISABLE:
+        case DEFAULT_KEYS_SHORTCUT:
+            mDefaultKeySsb = null;      // not used in these modes
+            break;
+        case DEFAULT_KEYS_DIALER:
+        case DEFAULT_KEYS_SEARCH_LOCAL:
+        case DEFAULT_KEYS_SEARCH_GLOBAL:
+            mDefaultKeySsb = new SpannableStringBuilder();
+            Selection.setSelection(mDefaultKeySsb,0);
+            break;
+        default:
+            throw new IllegalArgumentException();
+        }
+    }
+
+    /**
+     * Called when a key was pressed down and not handled by any of the views
+     * inside of the activity. So, for example, key presses while the cursor
+     * is inside a TextView will not trigger the event (unless it is a navigation
+     * to another object) because TextView handles its own key presses.
+     *
+     * <p>If the focused view didn't want this event, this method is called.
+     *
+     * <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK}
+     * by calling {@link #onBackPressed()}, though the behavior varies based
+     * on the application compatibility mode: for
+     * {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications,
+     * it will set up the dispatch to call {@link #onKeyUp} where the action
+     * will be performed; for earlier applications, it will perform the
+     * action immediately in on-down, as those versions of the platform
+     * behaved.
+     *
+     * <p>Other additional default key handling may be performed
+     * if configured with {@link #setDefaultKeyMode}.
+     *
+     * @return Return <code>true</code> to prevent this event from being propagated
+     * further, or <code>false</code> to indicate that you have not handled
+     * this event and it should continue to be propagated.
+     * @see #onKeyUp
+     * @see android.view.KeyEvent
+     */
+    public boolean onKeyDown(int keyCode, KeyEvent event)  {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            if (getApplicationInfo().targetSdkVersion
+                    >= Build.VERSION_CODES.ECLAIR) {
+                event.startTracking();
+            } else {
+                onBackPressed();
+            }
+            return true;
+        }
+
+        if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) {
+            return false;
+        } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) {
+            Window w = getWindow();
+            if (w.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+                    w.performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, keyCode, event,
+                            Menu.FLAG_ALWAYS_PERFORM_CLOSE)) {
+                return true;
+            }
+            return false;
+        } else if (keyCode == KeyEvent.KEYCODE_TAB) {
+            // Don't consume TAB here since it's used for navigation. Arrow keys
+            // aren't considered "typing keys" so they already won't get consumed.
+            return false;
+        } else {
+            // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_*
+            boolean clearSpannable = false;
+            boolean handled;
+            if ((event.getRepeatCount() != 0) || event.isSystem()) {
+                clearSpannable = true;
+                handled = false;
+            } else {
+                handled = TextKeyListener.getInstance().onKeyDown(
+                        null, mDefaultKeySsb, keyCode, event);
+                if (handled && mDefaultKeySsb.length() > 0) {
+                    // something useable has been typed - dispatch it now.
+
+                    final String str = mDefaultKeySsb.toString();
+                    clearSpannable = true;
+
+                    switch (mDefaultKeyMode) {
+                    case DEFAULT_KEYS_DIALER:
+                        Intent intent = new Intent(Intent.ACTION_DIAL,  Uri.parse("tel:" + str));
+                        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                        startActivity(intent);
+                        break;
+                    case DEFAULT_KEYS_SEARCH_LOCAL:
+                        startSearch(str, false, null, false);
+                        break;
+                    case DEFAULT_KEYS_SEARCH_GLOBAL:
+                        startSearch(str, false, null, true);
+                        break;
+                    }
+                }
+            }
+            if (clearSpannable) {
+                mDefaultKeySsb.clear();
+                mDefaultKeySsb.clearSpans();
+                Selection.setSelection(mDefaultKeySsb,0);
+            }
+            return handled;
+        }
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+     * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle
+     * the event).
+     *
+     * To receive this callback, you must return true from onKeyDown for the current
+     * event stream.
+     *
+     * @see KeyEvent.Callback#onKeyLongPress()
+     * @see KeyEvent.Callback#onKeyLongPress(int, KeyEvent)
+     */
+    public boolean onKeyLongPress(int keyCode, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Called when a key was released and not handled by any of the views
+     * inside of the activity. So, for example, key presses while the cursor
+     * is inside a TextView will not trigger the event (unless it is a navigation
+     * to another object) because TextView handles its own key presses.
+     *
+     * <p>The default implementation handles KEYCODE_BACK to stop the activity
+     * and go back.
+     *
+     * @return Return <code>true</code> to prevent this event from being propagated
+     * further, or <code>false</code> to indicate that you have not handled
+     * this event and it should continue to be propagated.
+     * @see #onKeyDown
+     * @see KeyEvent
+     */
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (getApplicationInfo().targetSdkVersion
+                >= Build.VERSION_CODES.ECLAIR) {
+            if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking()
+                    && !event.isCanceled()) {
+                onBackPressed();
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Default implementation of {@link KeyEvent.Callback#onKeyMultiple(int, int, KeyEvent)
+     * KeyEvent.Callback.onKeyMultiple()}: always returns false (doesn't handle
+     * the event).
+     */
+    public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Called when the activity has detected the user's press of the back
+     * key.  The default implementation simply finishes the current activity,
+     * but you can override this to do whatever you want.
+     */
+    public void onBackPressed() {
+        if (mActionBar != null && mActionBar.collapseActionView()) {
+            return;
+        }
+
+        FragmentManager fragmentManager = mFragments.getFragmentManager();
+
+        if (!fragmentManager.isStateSaved() && fragmentManager.popBackStackImmediate()) {
+            return;
+        }
+        if (!isTaskRoot()) {
+            // If the activity is not the root of the task, allow finish to proceed normally.
+            finishAfterTransition();
+            return;
+        }
+        try {
+            // Inform activity task manager that the activity received a back press
+            // while at the root of the task. This call allows ActivityTaskManager
+            // to intercept or defer finishing.
+            ActivityTaskManager.getService().onBackPressedOnTaskRoot(mToken,
+                    new IRequestFinishCallback.Stub() {
+                        public void requestFinish() {
+                            mHandler.post(() -> finishAfterTransition());
+                        }
+                    });
+        } catch (RemoteException e) {
+            finishAfterTransition();
+        }
+    }
+
+    /**
+     * Called when a key shortcut event is not handled by any of the views in the Activity.
+     * Override this method to implement global key shortcuts for the Activity.
+     * Key shortcuts can also be implemented by setting the
+     * {@link MenuItem#setShortcut(char, char) shortcut} property of menu items.
+     *
+     * @param keyCode The value in event.getKeyCode().
+     * @param event Description of the key event.
+     * @return True if the key shortcut was handled.
+     */
+    public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+        // Let the Action Bar have a chance at handling the shortcut.
+        ActionBar actionBar = getActionBar();
+        return (actionBar != null && actionBar.onKeyShortcut(keyCode, event));
+    }
+
+    /**
+     * Called when a touch screen event was not handled by any of the views
+     * under it.  This is most useful to process touch events that happen
+     * outside of your window bounds, where there is no view to receive it.
+     *
+     * @param event The touch screen event being processed.
+     *
+     * @return Return true if you have consumed the event, false if you haven't.
+     * The default implementation always returns false.
+     */
+    public boolean onTouchEvent(MotionEvent event) {
+        if (mWindow.shouldCloseOnTouch(this, event)) {
+            finish();
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Called when the trackball was moved and not handled by any of the
+     * views inside of the activity.  So, for example, if the trackball moves
+     * while focus is on a button, you will receive a call here because
+     * buttons do not normally do anything with trackball events.  The call
+     * here happens <em>before</em> trackball movements are converted to
+     * DPAD key events, which then get sent back to the view hierarchy, and
+     * will be processed at the point for things like focus navigation.
+     *
+     * @param event The trackball event being processed.
+     *
+     * @return Return true if you have consumed the event, false if you haven't.
+     * The default implementation always returns false.
+     */
+    public boolean onTrackballEvent(MotionEvent event) {
+        return false;
+    }
+
+    /**
+     * Called when a generic motion event was not handled by any of the
+     * views inside of the activity.
+     * <p>
+     * Generic motion events describe joystick movements, mouse hovers, track pad
+     * touches, scroll wheel movements and other input events.  The
+     * {@link MotionEvent#getSource() source} of the motion event specifies
+     * the class of input that was received.  Implementations of this method
+     * must examine the bits in the source before processing the event.
+     * The following code example shows how this is done.
+     * </p><p>
+     * Generic motion events with source class
+     * {@link android.view.InputDevice#SOURCE_CLASS_POINTER}
+     * are delivered to the view under the pointer.  All other generic motion events are
+     * delivered to the focused view.
+     * </p><p>
+     * See {@link View#onGenericMotionEvent(MotionEvent)} for an example of how to
+     * handle this event.
+     * </p>
+     *
+     * @param event The generic motion event being processed.
+     *
+     * @return Return true if you have consumed the event, false if you haven't.
+     * The default implementation always returns false.
+     */
+    public boolean onGenericMotionEvent(MotionEvent event) {
+        return false;
+    }
+
+    /**
+     * Called whenever a key, touch, or trackball event is dispatched to the
+     * activity.  Implement this method if you wish to know that the user has
+     * interacted with the device in some way while your activity is running.
+     * This callback and {@link #onUserLeaveHint} are intended to help
+     * activities manage status bar notifications intelligently; specifically,
+     * for helping activities determine the proper time to cancel a notification.
+     *
+     * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
+     * be accompanied by calls to {@link #onUserInteraction}.  This
+     * ensures that your activity will be told of relevant user activity such
+     * as pulling down the notification pane and touching an item there.
+     *
+     * <p>Note that this callback will be invoked for the touch down action
+     * that begins a touch gesture, but may not be invoked for the touch-moved
+     * and touch-up actions that follow.
+     *
+     * @see #onUserLeaveHint()
+     */
+    public void onUserInteraction() {
+    }
+
+    public void onWindowAttributesChanged(WindowManager.LayoutParams params) {
+        // Update window manager if: we have a view, that view is
+        // attached to its parent (which will be a RootView), and
+        // this activity is not embedded.
+        if (mParent == null) {
+            View decor = mDecor;
+            if (decor != null && decor.getParent() != null) {
+                getWindowManager().updateViewLayout(decor, params);
+                if (mContentCaptureManager != null) {
+                    mContentCaptureManager.updateWindowAttributes(params);
+                }
+            }
+        }
+    }
+
+    public void onContentChanged() {
+    }
+
+    /**
+     * Called when the current {@link Window} of the activity gains or loses
+     * focus. This is the best indicator of whether this activity is the entity
+     * with which the user actively interacts. The default implementation
+     * clears the key tracking state, so should always be called.
+     *
+     * <p>Note that this provides information about global focus state, which
+     * is managed independently of activity lifecycle.  As such, while focus
+     * changes will generally have some relation to lifecycle changes (an
+     * activity that is stopped will not generally get window focus), you
+     * should not rely on any particular order between the callbacks here and
+     * those in the other lifecycle methods such as {@link #onResume}.
+     *
+     * <p>As a general rule, however, a foreground activity will have window
+     * focus...  unless it has displayed other dialogs or popups that take
+     * input focus, in which case the activity itself will not have focus
+     * when the other windows have it.  Likewise, the system may display
+     * system-level windows (such as the status bar notification panel or
+     * a system alert) which will temporarily take window input focus without
+     * pausing the foreground activity.
+     *
+     * <p>Starting with {@link android.os.Build.VERSION_CODES#Q} there can be
+     * multiple resumed activities at the same time in multi-window mode, so
+     * resumed state does not guarantee window focus even if there are no
+     * overlays above.
+     *
+     * <p>If the intent is to know when an activity is the topmost active, the
+     * one the user interacted with last among all activities but not including
+     * non-activity windows like dialogs and popups, then
+     * {@link #onTopResumedActivityChanged(boolean)} should be used. On platform
+     * versions prior to {@link android.os.Build.VERSION_CODES#Q},
+     * {@link #onResume} is the best indicator.
+     *
+     * @param hasFocus Whether the window of this activity has focus.
+     *
+     * @see #hasWindowFocus()
+     * @see #onResume
+     * @see View#onWindowFocusChanged(boolean)
+     * @see #onTopResumedActivityChanged(boolean)
+     */
+    public void onWindowFocusChanged(boolean hasFocus) {
+    }
+
+    /**
+     * Called when the main window associated with the activity has been
+     * attached to the window manager.
+     * See {@link View#onAttachedToWindow() View.onAttachedToWindow()}
+     * for more information.
+     * @see View#onAttachedToWindow
+     */
+    public void onAttachedToWindow() {
+    }
+
+    /**
+     * Called when the main window associated with the activity has been
+     * detached from the window manager.
+     * See {@link View#onDetachedFromWindow() View.onDetachedFromWindow()}
+     * for more information.
+     * @see View#onDetachedFromWindow
+     */
+    public void onDetachedFromWindow() {
+    }
+
+    /**
+     * Returns true if this activity's <em>main</em> window currently has window focus.
+     * Note that this is not the same as the view itself having focus.
+     *
+     * @return True if this activity's main window currently has window focus.
+     *
+     * @see #onWindowAttributesChanged(android.view.WindowManager.LayoutParams)
+     */
+    public boolean hasWindowFocus() {
+        Window w = getWindow();
+        if (w != null) {
+            View d = w.getDecorView();
+            if (d != null) {
+                return d.hasWindowFocus();
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Called when the main window associated with the activity has been dismissed.
+     * @hide
+     */
+    @Override
+    public void onWindowDismissed(boolean finishTask, boolean suppressWindowTransition) {
+        finish(finishTask ? FINISH_TASK_WITH_ACTIVITY : DONT_FINISH_TASK_WITH_ACTIVITY);
+        if (suppressWindowTransition) {
+            overridePendingTransition(0, 0);
+        }
+    }
+
+
+    /**
+     * Moves the activity between {@link WindowConfiguration#WINDOWING_MODE_FREEFORM} windowing mode
+     * and {@link WindowConfiguration#WINDOWING_MODE_FULLSCREEN}.
+     *
+     * @hide
+     */
+    @Override
+    public void toggleFreeformWindowingMode() throws RemoteException {
+        ActivityTaskManager.getService().toggleFreeformWindowingMode(mToken);
+    }
+
+    /**
+     * Puts the activity in picture-in-picture mode if the activity supports.
+     * @see android.R.attr#supportsPictureInPicture
+     * @hide
+     */
+    @Override
+    public void enterPictureInPictureModeIfPossible() {
+        if (mActivityInfo.supportsPictureInPicture()) {
+            enterPictureInPictureMode();
+        }
+    }
+
+    /**
+     * Called to process key events.  You can override this to intercept all
+     * key events before they are dispatched to the window.  Be sure to call
+     * this implementation for key events that should be handled normally.
+     *
+     * @param event The key event.
+     *
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        onUserInteraction();
+
+        // Let action bars open menus in response to the menu key prioritized over
+        // the window handling it
+        final int keyCode = event.getKeyCode();
+        if (keyCode == KeyEvent.KEYCODE_MENU &&
+                mActionBar != null && mActionBar.onMenuKeyEvent(event)) {
+            return true;
+        }
+
+        Window win = getWindow();
+        if (win.superDispatchKeyEvent(event)) {
+            return true;
+        }
+        View decor = mDecor;
+        if (decor == null) decor = win.getDecorView();
+        return event.dispatch(this, decor != null
+                ? decor.getKeyDispatcherState() : null, this);
+    }
+
+    /**
+     * Called to process a key shortcut event.
+     * You can override this to intercept all key shortcut events before they are
+     * dispatched to the window.  Be sure to call this implementation for key shortcut
+     * events that should be handled normally.
+     *
+     * @param event The key shortcut event.
+     * @return True if this event was consumed.
+     */
+    public boolean dispatchKeyShortcutEvent(KeyEvent event) {
+        onUserInteraction();
+        if (getWindow().superDispatchKeyShortcutEvent(event)) {
+            return true;
+        }
+        return onKeyShortcut(event.getKeyCode(), event);
+    }
+
+    /**
+     * Called to process touch screen events.  You can override this to
+     * intercept all touch screen events before they are dispatched to the
+     * window.  Be sure to call this implementation for touch screen events
+     * that should be handled normally.
+     *
+     * @param ev The touch screen event.
+     *
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchTouchEvent(MotionEvent ev) {
+        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
+            onUserInteraction();
+        }
+        if (getWindow().superDispatchTouchEvent(ev)) {
+            return true;
+        }
+        return onTouchEvent(ev);
+    }
+
+    /**
+     * Called to process trackball events.  You can override this to
+     * intercept all trackball events before they are dispatched to the
+     * window.  Be sure to call this implementation for trackball events
+     * that should be handled normally.
+     *
+     * @param ev The trackball event.
+     *
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchTrackballEvent(MotionEvent ev) {
+        onUserInteraction();
+        if (getWindow().superDispatchTrackballEvent(ev)) {
+            return true;
+        }
+        return onTrackballEvent(ev);
+    }
+
+    /**
+     * Called to process generic motion events.  You can override this to
+     * intercept all generic motion events before they are dispatched to the
+     * window.  Be sure to call this implementation for generic motion events
+     * that should be handled normally.
+     *
+     * @param ev The generic motion event.
+     *
+     * @return boolean Return true if this event was consumed.
+     */
+    public boolean dispatchGenericMotionEvent(MotionEvent ev) {
+        onUserInteraction();
+        if (getWindow().superDispatchGenericMotionEvent(ev)) {
+            return true;
+        }
+        return onGenericMotionEvent(ev);
+    }
+
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        event.setClassName(getClass().getName());
+        event.setPackageName(getPackageName());
+
+        LayoutParams params = getWindow().getAttributes();
+        boolean isFullScreen = (params.width == LayoutParams.MATCH_PARENT) &&
+            (params.height == LayoutParams.MATCH_PARENT);
+        event.setFullScreen(isFullScreen);
+
+        CharSequence title = getTitle();
+        if (!TextUtils.isEmpty(title)) {
+           event.getText().add(title);
+        }
+
+        return true;
+    }
+
+    /**
+     * Default implementation of
+     * {@link android.view.Window.Callback#onCreatePanelView}
+     * for activities. This
+     * simply returns null so that all panel sub-windows will have the default
+     * menu behavior.
+     */
+    @Nullable
+    public View onCreatePanelView(int featureId) {
+        return null;
+    }
+
+    /**
+     * Default implementation of
+     * {@link android.view.Window.Callback#onCreatePanelMenu}
+     * for activities.  This calls through to the new
+     * {@link #onCreateOptionsMenu} method for the
+     * {@link android.view.Window#FEATURE_OPTIONS_PANEL} panel,
+     * so that subclasses of Activity don't need to deal with feature codes.
+     */
+    public boolean onCreatePanelMenu(int featureId, @NonNull Menu menu) {
+        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+            boolean show = onCreateOptionsMenu(menu);
+            show |= mFragments.dispatchCreateOptionsMenu(menu, getMenuInflater());
+            return show;
+        }
+        return false;
+    }
+
+    /**
+     * Default implementation of
+     * {@link android.view.Window.Callback#onPreparePanel}
+     * for activities.  This
+     * calls through to the new {@link #onPrepareOptionsMenu} method for the
+     * {@link android.view.Window#FEATURE_OPTIONS_PANEL}
+     * panel, so that subclasses of
+     * Activity don't need to deal with feature codes.
+     */
+    public boolean onPreparePanel(int featureId, @Nullable View view, @NonNull Menu menu) {
+        if (featureId == Window.FEATURE_OPTIONS_PANEL) {
+            boolean goforit = onPrepareOptionsMenu(menu);
+            goforit |= mFragments.dispatchPrepareOptionsMenu(menu);
+            return goforit;
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @return The default implementation returns true.
+     */
+    @Override
+    public boolean onMenuOpened(int featureId, @NonNull Menu menu) {
+        if (featureId == Window.FEATURE_ACTION_BAR) {
+            initWindowDecorActionBar();
+            if (mActionBar != null) {
+                mActionBar.dispatchMenuVisibilityChanged(true);
+            } else {
+                Log.e(TAG, "Tried to open action bar menu with no action bar");
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Default implementation of
+     * {@link android.view.Window.Callback#onMenuItemSelected}
+     * for activities.  This calls through to the new
+     * {@link #onOptionsItemSelected} method for the
+     * {@link android.view.Window#FEATURE_OPTIONS_PANEL}
+     * panel, so that subclasses of
+     * Activity don't need to deal with feature codes.
+     */
+    public boolean onMenuItemSelected(int featureId, @NonNull MenuItem item) {
+        CharSequence titleCondensed = item.getTitleCondensed();
+
+        switch (featureId) {
+            case Window.FEATURE_OPTIONS_PANEL:
+                // Put event logging here so it gets called even if subclass
+                // doesn't call through to superclass's implmeentation of each
+                // of these methods below
+                if(titleCondensed != null) {
+                    EventLog.writeEvent(50000, 0, titleCondensed.toString());
+                }
+                if (onOptionsItemSelected(item)) {
+                    return true;
+                }
+                if (mFragments.dispatchOptionsItemSelected(item)) {
+                    return true;
+                }
+                if (item.getItemId() == android.R.id.home && mActionBar != null &&
+                        (mActionBar.getDisplayOptions() & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+                    if (mParent == null) {
+                        return onNavigateUp();
+                    } else {
+                        return mParent.onNavigateUpFromChild(this);
+                    }
+                }
+                return false;
+
+            case Window.FEATURE_CONTEXT_MENU:
+                if(titleCondensed != null) {
+                    EventLog.writeEvent(50000, 1, titleCondensed.toString());
+                }
+                if (onContextItemSelected(item)) {
+                    return true;
+                }
+                return mFragments.dispatchContextItemSelected(item);
+
+            default:
+                return false;
+        }
+    }
+
+    /**
+     * Default implementation of
+     * {@link android.view.Window.Callback#onPanelClosed(int, Menu)} for
+     * activities. This calls through to {@link #onOptionsMenuClosed(Menu)}
+     * method for the {@link android.view.Window#FEATURE_OPTIONS_PANEL} panel,
+     * so that subclasses of Activity don't need to deal with feature codes.
+     * For context menus ({@link Window#FEATURE_CONTEXT_MENU}), the
+     * {@link #onContextMenuClosed(Menu)} will be called.
+     */
+    public void onPanelClosed(int featureId, @NonNull Menu menu) {
+        switch (featureId) {
+            case Window.FEATURE_OPTIONS_PANEL:
+                mFragments.dispatchOptionsMenuClosed(menu);
+                onOptionsMenuClosed(menu);
+                break;
+
+            case Window.FEATURE_CONTEXT_MENU:
+                onContextMenuClosed(menu);
+                break;
+
+            case Window.FEATURE_ACTION_BAR:
+                initWindowDecorActionBar();
+                mActionBar.dispatchMenuVisibilityChanged(false);
+                break;
+        }
+    }
+
+    /**
+     * Declare that the options menu has changed, so should be recreated.
+     * The {@link #onCreateOptionsMenu(Menu)} method will be called the next
+     * time it needs to be displayed.
+     */
+    public void invalidateOptionsMenu() {
+        if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+                (mActionBar == null || !mActionBar.invalidateOptionsMenu())) {
+            mWindow.invalidatePanelMenu(Window.FEATURE_OPTIONS_PANEL);
+        }
+    }
+
+    /**
+     * Initialize the contents of the Activity's standard options menu.  You
+     * should place your menu items in to <var>menu</var>.
+     *
+     * <p>This is only called once, the first time the options menu is
+     * displayed.  To update the menu every time it is displayed, see
+     * {@link #onPrepareOptionsMenu}.
+     *
+     * <p>The default implementation populates the menu with standard system
+     * menu items.  These are placed in the {@link Menu#CATEGORY_SYSTEM} group so that
+     * they will be correctly ordered with application-defined menu items.
+     * Deriving classes should always call through to the base implementation.
+     *
+     * <p>You can safely hold on to <var>menu</var> (and any items created
+     * from it), making modifications to it as desired, until the next
+     * time onCreateOptionsMenu() is called.
+     *
+     * <p>When you add items to the menu, you can implement the Activity's
+     * {@link #onOptionsItemSelected} method to handle them there.
+     *
+     * @param menu The options menu in which you place your items.
+     *
+     * @return You must return true for the menu to be displayed;
+     *         if you return false it will not be shown.
+     *
+     * @see #onPrepareOptionsMenu
+     * @see #onOptionsItemSelected
+     */
+    public boolean onCreateOptionsMenu(Menu menu) {
+        if (mParent != null) {
+            return mParent.onCreateOptionsMenu(menu);
+        }
+        return true;
+    }
+
+    /**
+     * Prepare the Screen's standard options menu to be displayed.  This is
+     * called right before the menu is shown, every time it is shown.  You can
+     * use this method to efficiently enable/disable items or otherwise
+     * dynamically modify the contents.
+     *
+     * <p>The default implementation updates the system menu items based on the
+     * activity's state.  Deriving classes should always call through to the
+     * base class implementation.
+     *
+     * @param menu The options menu as last shown or first initialized by
+     *             onCreateOptionsMenu().
+     *
+     * @return You must return true for the menu to be displayed;
+     *         if you return false it will not be shown.
+     *
+     * @see #onCreateOptionsMenu
+     */
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        if (mParent != null) {
+            return mParent.onPrepareOptionsMenu(menu);
+        }
+        return true;
+    }
+
+    /**
+     * This hook is called whenever an item in your options menu is selected.
+     * The default implementation simply returns false to have the normal
+     * processing happen (calling the item's Runnable or sending a message to
+     * its Handler as appropriate).  You can use this method for any items
+     * for which you would like to do processing without those other
+     * facilities.
+     *
+     * <p>Derived classes should call through to the base class for it to
+     * perform the default menu handling.</p>
+     *
+     * @param item The menu item that was selected.
+     *
+     * @return boolean Return false to allow normal menu processing to
+     *         proceed, true to consume it here.
+     *
+     * @see #onCreateOptionsMenu
+     */
+    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
+        if (mParent != null) {
+            return mParent.onOptionsItemSelected(item);
+        }
+        return false;
+    }
+
+    /**
+     * This method is called whenever the user chooses to navigate Up within your application's
+     * activity hierarchy from the action bar.
+     *
+     * <p>If the attribute {@link android.R.attr#parentActivityName parentActivityName}
+     * was specified in the manifest for this activity or an activity-alias to it,
+     * default Up navigation will be handled automatically. If any activity
+     * along the parent chain requires extra Intent arguments, the Activity subclass
+     * should override the method {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)}
+     * to supply those arguments.</p>
+     *
+     * <p>See <a href="{@docRoot}guide/components/tasks-and-back-stack.html">Tasks and Back Stack</a>
+     * from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a>
+     * from the design guide for more information about navigating within your app.</p>
+     *
+     * <p>See the {@link TaskStackBuilder} class and the Activity methods
+     * {@link #getParentActivityIntent()}, {@link #shouldUpRecreateTask(Intent)}, and
+     * {@link #navigateUpTo(Intent)} for help implementing custom Up navigation.
+     * The AppNavigation sample application in the Android SDK is also available for reference.</p>
+     *
+     * @return true if Up navigation completed successfully and this Activity was finished,
+     *         false otherwise.
+     */
+    public boolean onNavigateUp() {
+        // Automatically handle hierarchical Up navigation if the proper
+        // metadata is available.
+        Intent upIntent = getParentActivityIntent();
+        if (upIntent != null) {
+            if (mActivityInfo.taskAffinity == null) {
+                // Activities with a null affinity are special; they really shouldn't
+                // specify a parent activity intent in the first place. Just finish
+                // the current activity and call it a day.
+                finish();
+            } else if (shouldUpRecreateTask(upIntent)) {
+                TaskStackBuilder b = TaskStackBuilder.create(this);
+                onCreateNavigateUpTaskStack(b);
+                onPrepareNavigateUpTaskStack(b);
+                b.startActivities();
+
+                // We can't finishAffinity if we have a result.
+                // Fall back and simply finish the current activity instead.
+                if (mResultCode != RESULT_CANCELED || mResultData != null) {
+                    // Tell the developer what's going on to avoid hair-pulling.
+                    Log.i(TAG, "onNavigateUp only finishing topmost activity to return a result");
+                    finish();
+                } else {
+                    finishAffinity();
+                }
+            } else {
+                navigateUpTo(upIntent);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * This is called when a child activity of this one attempts to navigate up.
+     * The default implementation simply calls onNavigateUp() on this activity (the parent).
+     *
+     * @param child The activity making the call.
+     */
+    public boolean onNavigateUpFromChild(Activity child) {
+        return onNavigateUp();
+    }
+
+    /**
+     * Define the synthetic task stack that will be generated during Up navigation from
+     * a different task.
+     *
+     * <p>The default implementation of this method adds the parent chain of this activity
+     * as specified in the manifest to the supplied {@link TaskStackBuilder}. Applications
+     * may choose to override this method to construct the desired task stack in a different
+     * way.</p>
+     *
+     * <p>This method will be invoked by the default implementation of {@link #onNavigateUp()}
+     * if {@link #shouldUpRecreateTask(Intent)} returns true when supplied with the intent
+     * returned by {@link #getParentActivityIntent()}.</p>
+     *
+     * <p>Applications that wish to supply extra Intent parameters to the parent stack defined
+     * by the manifest should override {@link #onPrepareNavigateUpTaskStack(TaskStackBuilder)}.</p>
+     *
+     * @param builder An empty TaskStackBuilder - the application should add intents representing
+     *                the desired task stack
+     */
+    public void onCreateNavigateUpTaskStack(TaskStackBuilder builder) {
+        builder.addParentStack(this);
+    }
+
+    /**
+     * Prepare the synthetic task stack that will be generated during Up navigation
+     * from a different task.
+     *
+     * <p>This method receives the {@link TaskStackBuilder} with the constructed series of
+     * Intents as generated by {@link #onCreateNavigateUpTaskStack(TaskStackBuilder)}.
+     * If any extra data should be added to these intents before launching the new task,
+     * the application should override this method and add that data here.</p>
+     *
+     * @param builder A TaskStackBuilder that has been populated with Intents by
+     *                onCreateNavigateUpTaskStack.
+     */
+    public void onPrepareNavigateUpTaskStack(TaskStackBuilder builder) {
+    }
+
+    /**
+     * This hook is called whenever the options menu is being closed (either by the user canceling
+     * the menu with the back/menu button, or when an item is selected).
+     *
+     * @param menu The options menu as last shown or first initialized by
+     *             onCreateOptionsMenu().
+     */
+    public void onOptionsMenuClosed(Menu menu) {
+        if (mParent != null) {
+            mParent.onOptionsMenuClosed(menu);
+        }
+    }
+
+    /**
+     * Programmatically opens the options menu. If the options menu is already
+     * open, this method does nothing.
+     */
+    public void openOptionsMenu() {
+        if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+                (mActionBar == null || !mActionBar.openOptionsMenu())) {
+            mWindow.openPanel(Window.FEATURE_OPTIONS_PANEL, null);
+        }
+    }
+
+    /**
+     * Progammatically closes the options menu. If the options menu is already
+     * closed, this method does nothing.
+     */
+    public void closeOptionsMenu() {
+        if (mWindow.hasFeature(Window.FEATURE_OPTIONS_PANEL) &&
+                (mActionBar == null || !mActionBar.closeOptionsMenu())) {
+            mWindow.closePanel(Window.FEATURE_OPTIONS_PANEL);
+        }
+    }
+
+    /**
+     * Called when a context menu for the {@code view} is about to be shown.
+     * Unlike {@link #onCreateOptionsMenu(Menu)}, this will be called every
+     * time the context menu is about to be shown and should be populated for
+     * the view (or item inside the view for {@link AdapterView} subclasses,
+     * this can be found in the {@code menuInfo})).
+     * <p>
+     * Use {@link #onContextItemSelected(android.view.MenuItem)} to know when an
+     * item has been selected.
+     * <p>
+     * It is not safe to hold onto the context menu after this method returns.
+     *
+     */
+    public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
+    }
+
+    /**
+     * Registers a context menu to be shown for the given view (multiple views
+     * can show the context menu). This method will set the
+     * {@link OnCreateContextMenuListener} on the view to this activity, so
+     * {@link #onCreateContextMenu(ContextMenu, View, ContextMenuInfo)} will be
+     * called when it is time to show the context menu.
+     *
+     * @see #unregisterForContextMenu(View)
+     * @param view The view that should show a context menu.
+     */
+    public void registerForContextMenu(View view) {
+        view.setOnCreateContextMenuListener(this);
+    }
+
+    /**
+     * Prevents a context menu to be shown for the given view. This method will remove the
+     * {@link OnCreateContextMenuListener} on the view.
+     *
+     * @see #registerForContextMenu(View)
+     * @param view The view that should stop showing a context menu.
+     */
+    public void unregisterForContextMenu(View view) {
+        view.setOnCreateContextMenuListener(null);
+    }
+
+    /**
+     * Programmatically opens the context menu for a particular {@code view}.
+     * The {@code view} should have been added via
+     * {@link #registerForContextMenu(View)}.
+     *
+     * @param view The view to show the context menu for.
+     */
+    public void openContextMenu(View view) {
+        view.showContextMenu();
+    }
+
+    /**
+     * Programmatically closes the most recently opened context menu, if showing.
+     */
+    public void closeContextMenu() {
+        if (mWindow.hasFeature(Window.FEATURE_CONTEXT_MENU)) {
+            mWindow.closePanel(Window.FEATURE_CONTEXT_MENU);
+        }
+    }
+
+    /**
+     * This hook is called whenever an item in a context menu is selected. The
+     * default implementation simply returns false to have the normal processing
+     * happen (calling the item's Runnable or sending a message to its Handler
+     * as appropriate). You can use this method for any items for which you
+     * would like to do processing without those other facilities.
+     * <p>
+     * Use {@link MenuItem#getMenuInfo()} to get extra information set by the
+     * View that added this menu item.
+     * <p>
+     * Derived classes should call through to the base class for it to perform
+     * the default menu handling.
+     *
+     * @param item The context menu item that was selected.
+     * @return boolean Return false to allow normal context menu processing to
+     *         proceed, true to consume it here.
+     */
+    public boolean onContextItemSelected(@NonNull MenuItem item) {
+        if (mParent != null) {
+            return mParent.onContextItemSelected(item);
+        }
+        return false;
+    }
+
+    /**
+     * This hook is called whenever the context menu is being closed (either by
+     * the user canceling the menu with the back/menu button, or when an item is
+     * selected).
+     *
+     * @param menu The context menu that is being closed.
+     */
+    public void onContextMenuClosed(@NonNull Menu menu) {
+        if (mParent != null) {
+            mParent.onContextMenuClosed(menu);
+        }
+    }
+
+    /**
+     * @deprecated Old no-arguments version of {@link #onCreateDialog(int, Bundle)}.
+     */
+    @Deprecated
+    protected Dialog onCreateDialog(int id) {
+        return null;
+    }
+
+    /**
+     * Callback for creating dialogs that are managed (saved and restored) for you
+     * by the activity.  The default implementation calls through to
+     * {@link #onCreateDialog(int)} for compatibility.
+     *
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using a {@link DialogFragment} instead.</em>
+     *
+     * <p>If you use {@link #showDialog(int)}, the activity will call through to
+     * this method the first time, and hang onto it thereafter.  Any dialog
+     * that is created by this method will automatically be saved and restored
+     * for you, including whether it is showing.
+     *
+     * <p>If you would like the activity to manage saving and restoring dialogs
+     * for you, you should override this method and handle any ids that are
+     * passed to {@link #showDialog}.
+     *
+     * <p>If you would like an opportunity to prepare your dialog before it is shown,
+     * override {@link #onPrepareDialog(int, Dialog, Bundle)}.
+     *
+     * @param id The id of the dialog.
+     * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}.
+     * @return The dialog.  If you return null, the dialog will not be created.
+     *
+     * @see #onPrepareDialog(int, Dialog, Bundle)
+     * @see #showDialog(int, Bundle)
+     * @see #dismissDialog(int)
+     * @see #removeDialog(int)
+     *
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     */
+    @Nullable
+    @Deprecated
+    protected Dialog onCreateDialog(int id, Bundle args) {
+        return onCreateDialog(id);
+    }
+
+    /**
+     * @deprecated Old no-arguments version of
+     * {@link #onPrepareDialog(int, Dialog, Bundle)}.
+     */
+    @Deprecated
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        dialog.setOwnerActivity(this);
+    }
+
+    /**
+     * Provides an opportunity to prepare a managed dialog before it is being
+     * shown.  The default implementation calls through to
+     * {@link #onPrepareDialog(int, Dialog)} for compatibility.
+     *
+     * <p>
+     * Override this if you need to update a managed dialog based on the state
+     * of the application each time it is shown. For example, a time picker
+     * dialog might want to be updated with the current time. You should call
+     * through to the superclass's implementation. The default implementation
+     * will set this Activity as the owner activity on the Dialog.
+     *
+     * @param id The id of the managed dialog.
+     * @param dialog The dialog.
+     * @param args The dialog arguments provided to {@link #showDialog(int, Bundle)}.
+     * @see #onCreateDialog(int, Bundle)
+     * @see #showDialog(int)
+     * @see #dismissDialog(int)
+     * @see #removeDialog(int)
+     *
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     */
+    @Deprecated
+    protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
+        onPrepareDialog(id, dialog);
+    }
+
+    /**
+     * Simple version of {@link #showDialog(int, Bundle)} that does not
+     * take any arguments.  Simply calls {@link #showDialog(int, Bundle)}
+     * with null arguments.
+     *
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     */
+    @Deprecated
+    public final void showDialog(int id) {
+        showDialog(id, null);
+    }
+
+    /**
+     * Show a dialog managed by this activity.  A call to {@link #onCreateDialog(int, Bundle)}
+     * will be made with the same id the first time this is called for a given
+     * id.  From thereafter, the dialog will be automatically saved and restored.
+     *
+     * <em>If you are targeting {@link android.os.Build.VERSION_CODES#HONEYCOMB}
+     * or later, consider instead using a {@link DialogFragment} instead.</em>
+     *
+     * <p>Each time a dialog is shown, {@link #onPrepareDialog(int, Dialog, Bundle)} will
+     * be made to provide an opportunity to do any timely preparation.
+     *
+     * @param id The id of the managed dialog.
+     * @param args Arguments to pass through to the dialog.  These will be saved
+     * and restored for you.  Note that if the dialog is already created,
+     * {@link #onCreateDialog(int, Bundle)} will not be called with the new
+     * arguments but {@link #onPrepareDialog(int, Dialog, Bundle)} will be.
+     * If you need to rebuild the dialog, call {@link #removeDialog(int)} first.
+     * @return Returns true if the Dialog was created; false is returned if
+     * it is not created because {@link #onCreateDialog(int, Bundle)} returns false.
+     *
+     * @see Dialog
+     * @see #onCreateDialog(int, Bundle)
+     * @see #onPrepareDialog(int, Dialog, Bundle)
+     * @see #dismissDialog(int)
+     * @see #removeDialog(int)
+     *
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     */
+    @Deprecated
+    public final boolean showDialog(int id, Bundle args) {
+        if (mManagedDialogs == null) {
+            mManagedDialogs = new SparseArray<ManagedDialog>();
+        }
+        ManagedDialog md = mManagedDialogs.get(id);
+        if (md == null) {
+            md = new ManagedDialog();
+            md.mDialog = createDialog(id, null, args);
+            if (md.mDialog == null) {
+                return false;
+            }
+            mManagedDialogs.put(id, md);
+        }
+
+        md.mArgs = args;
+        onPrepareDialog(id, md.mDialog, args);
+        md.mDialog.show();
+        return true;
+    }
+
+    /**
+     * Dismiss a dialog that was previously shown via {@link #showDialog(int)}.
+     *
+     * @param id The id of the managed dialog.
+     *
+     * @throws IllegalArgumentException if the id was not previously shown via
+     *   {@link #showDialog(int)}.
+     *
+     * @see #onCreateDialog(int, Bundle)
+     * @see #onPrepareDialog(int, Dialog, Bundle)
+     * @see #showDialog(int)
+     * @see #removeDialog(int)
+     *
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     */
+    @Deprecated
+    public final void dismissDialog(int id) {
+        if (mManagedDialogs == null) {
+            throw missingDialog(id);
+        }
+
+        final ManagedDialog md = mManagedDialogs.get(id);
+        if (md == null) {
+            throw missingDialog(id);
+        }
+        md.mDialog.dismiss();
+    }
+
+    /**
+     * Creates an exception to throw if a user passed in a dialog id that is
+     * unexpected.
+     */
+    private IllegalArgumentException missingDialog(int id) {
+        return new IllegalArgumentException("no dialog with id " + id + " was ever "
+                + "shown via Activity#showDialog");
+    }
+
+    /**
+     * Removes any internal references to a dialog managed by this Activity.
+     * If the dialog is showing, it will dismiss it as part of the clean up.
+     *
+     * <p>This can be useful if you know that you will never show a dialog again and
+     * want to avoid the overhead of saving and restoring it in the future.
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, this function
+     * will not throw an exception if you try to remove an ID that does not
+     * currently have an associated dialog.</p>
+     *
+     * @param id The id of the managed dialog.
+     *
+     * @see #onCreateDialog(int, Bundle)
+     * @see #onPrepareDialog(int, Dialog, Bundle)
+     * @see #showDialog(int)
+     * @see #dismissDialog(int)
+     *
+     * @deprecated Use the new {@link DialogFragment} class with
+     * {@link FragmentManager} instead; this is also
+     * available on older platforms through the Android compatibility package.
+     */
+    @Deprecated
+    public final void removeDialog(int id) {
+        if (mManagedDialogs != null) {
+            final ManagedDialog md = mManagedDialogs.get(id);
+            if (md != null) {
+                md.mDialog.dismiss();
+                mManagedDialogs.remove(id);
+            }
+        }
+    }
+
+    /**
+     * This hook is called when the user signals the desire to start a search.
+     *
+     * <p>You can use this function as a simple way to launch the search UI, in response to a
+     * menu item, search button, or other widgets within your activity. Unless overidden,
+     * calling this function is the same as calling
+     * {@link #startSearch startSearch(null, false, null, false)}, which launches
+     * search for the current activity as specified in its manifest, see {@link SearchManager}.
+     *
+     * <p>You can override this function to force global search, e.g. in response to a dedicated
+     * search key, or to block search entirely (by simply returning false).
+     *
+     * <p>Note: when running in a {@link Configuration#UI_MODE_TYPE_TELEVISION} or
+     * {@link Configuration#UI_MODE_TYPE_WATCH}, the default implementation changes to simply
+     * return false and you must supply your own custom implementation if you want to support
+     * search.
+     *
+     * @param searchEvent The {@link SearchEvent} that signaled this search.
+     * @return Returns {@code true} if search launched, and {@code false} if the activity does
+     * not respond to search.  The default implementation always returns {@code true}, except
+     * when in {@link Configuration#UI_MODE_TYPE_TELEVISION} mode where it returns false.
+     *
+     * @see android.app.SearchManager
+     */
+    public boolean onSearchRequested(@Nullable SearchEvent searchEvent) {
+        mSearchEvent = searchEvent;
+        boolean result = onSearchRequested();
+        mSearchEvent = null;
+        return result;
+    }
+
+    /**
+     * @see #onSearchRequested(SearchEvent)
+     */
+    public boolean onSearchRequested() {
+        final int uiMode = getResources().getConfiguration().uiMode
+            & Configuration.UI_MODE_TYPE_MASK;
+        if (uiMode != Configuration.UI_MODE_TYPE_TELEVISION
+                && uiMode != Configuration.UI_MODE_TYPE_WATCH) {
+            startSearch(null, false, null, false);
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * During the onSearchRequested() callbacks, this function will return the
+     * {@link SearchEvent} that triggered the callback, if it exists.
+     *
+     * @return SearchEvent The SearchEvent that triggered the {@link
+     *                    #onSearchRequested} callback.
+     */
+    public final SearchEvent getSearchEvent() {
+        return mSearchEvent;
+    }
+
+    /**
+     * This hook is called to launch the search UI.
+     *
+     * <p>It is typically called from onSearchRequested(), either directly from
+     * Activity.onSearchRequested() or from an overridden version in any given
+     * Activity.  If your goal is simply to activate search, it is preferred to call
+     * onSearchRequested(), which may have been overridden elsewhere in your Activity.  If your goal
+     * is to inject specific data such as context data, it is preferred to <i>override</i>
+     * onSearchRequested(), so that any callers to it will benefit from the override.
+     *
+     * <p>Note: when running in a {@link Configuration#UI_MODE_TYPE_WATCH}, use of this API is
+     * not supported.
+     *
+     * @param initialQuery Any non-null non-empty string will be inserted as
+     * pre-entered text in the search query box.
+     * @param selectInitialQuery If true, the initial query will be preselected, which means that
+     * any further typing will replace it.  This is useful for cases where an entire pre-formed
+     * query is being inserted.  If false, the selection point will be placed at the end of the
+     * inserted query.  This is useful when the inserted query is text that the user entered,
+     * and the user would expect to be able to keep typing.  <i>This parameter is only meaningful
+     * if initialQuery is a non-empty string.</i>
+     * @param appSearchData An application can insert application-specific
+     * context here, in order to improve quality or specificity of its own
+     * searches.  This data will be returned with SEARCH intent(s).  Null if
+     * no extra data is required.
+     * @param globalSearch If false, this will only launch the search that has been specifically
+     * defined by the application (which is usually defined as a local search).  If no default
+     * search is defined in the current application or activity, global search will be launched.
+     * If true, this will always launch a platform-global (e.g. web-based) search instead.
+     *
+     * @see android.app.SearchManager
+     * @see #onSearchRequested
+     */
+    public void startSearch(@Nullable String initialQuery, boolean selectInitialQuery,
+            @Nullable Bundle appSearchData, boolean globalSearch) {
+        ensureSearchManager();
+        mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(),
+                appSearchData, globalSearch);
+    }
+
+    /**
+     * Similar to {@link #startSearch}, but actually fires off the search query after invoking
+     * the search dialog.  Made available for testing purposes.
+     *
+     * @param query The query to trigger.  If empty, the request will be ignored.
+     * @param appSearchData An application can insert application-specific
+     * context here, in order to improve quality or specificity of its own
+     * searches.  This data will be returned with SEARCH intent(s).  Null if
+     * no extra data is required.
+     */
+    public void triggerSearch(String query, @Nullable Bundle appSearchData) {
+        ensureSearchManager();
+        mSearchManager.triggerSearch(query, getComponentName(), appSearchData);
+    }
+
+    /**
+     * Request that key events come to this activity. Use this if your
+     * activity has no views with focus, but the activity still wants
+     * a chance to process key events.
+     *
+     * @see android.view.Window#takeKeyEvents
+     */
+    public void takeKeyEvents(boolean get) {
+        getWindow().takeKeyEvents(get);
+    }
+
+    /**
+     * Enable extended window features.  This is a convenience for calling
+     * {@link android.view.Window#requestFeature getWindow().requestFeature()}.
+     *
+     * @param featureId The desired feature as defined in
+     *                  {@link android.view.Window}.
+     * @return Returns true if the requested feature is supported and now
+     *         enabled.
+     *
+     * @see android.view.Window#requestFeature
+     */
+    public final boolean requestWindowFeature(int featureId) {
+        return getWindow().requestFeature(featureId);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawableResource}.
+     */
+    public final void setFeatureDrawableResource(int featureId, @DrawableRes int resId) {
+        getWindow().setFeatureDrawableResource(featureId, resId);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawableUri}.
+     */
+    public final void setFeatureDrawableUri(int featureId, Uri uri) {
+        getWindow().setFeatureDrawableUri(featureId, uri);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawable(int, Drawable)}.
+     */
+    public final void setFeatureDrawable(int featureId, Drawable drawable) {
+        getWindow().setFeatureDrawable(featureId, drawable);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#setFeatureDrawableAlpha}.
+     */
+    public final void setFeatureDrawableAlpha(int featureId, int alpha) {
+        getWindow().setFeatureDrawableAlpha(featureId, alpha);
+    }
+
+    /**
+     * Convenience for calling
+     * {@link android.view.Window#getLayoutInflater}.
+     */
+    @NonNull
+    public LayoutInflater getLayoutInflater() {
+        return getWindow().getLayoutInflater();
+    }
+
+    /**
+     * Returns a {@link MenuInflater} with this context.
+     */
+    @NonNull
+    public MenuInflater getMenuInflater() {
+        // Make sure that action views can get an appropriate theme.
+        if (mMenuInflater == null) {
+            initWindowDecorActionBar();
+            if (mActionBar != null) {
+                mMenuInflater = new MenuInflater(mActionBar.getThemedContext(), this);
+            } else {
+                mMenuInflater = new MenuInflater(this);
+            }
+        }
+        return mMenuInflater;
+    }
+
+    @Override
+    public void setTheme(int resid) {
+        super.setTheme(resid);
+        mWindow.setTheme(resid);
+    }
+
+    @Override
+    protected void onApplyThemeResource(Resources.Theme theme, @StyleRes int resid,
+            boolean first) {
+        if (mParent == null) {
+            super.onApplyThemeResource(theme, resid, first);
+        } else {
+            try {
+                theme.setTo(mParent.getTheme());
+            } catch (Exception e) {
+                // Empty
+            }
+            theme.applyStyle(resid, false);
+        }
+
+        // Get the primary color and update the TaskDescription for this activity
+        TypedArray a = theme.obtainStyledAttributes(
+                com.android.internal.R.styleable.ActivityTaskDescription);
+        if (mTaskDescription.getPrimaryColor() == 0) {
+            int colorPrimary = a.getColor(
+                    com.android.internal.R.styleable.ActivityTaskDescription_colorPrimary, 0);
+            if (colorPrimary != 0 && Color.alpha(colorPrimary) == 0xFF) {
+                mTaskDescription.setPrimaryColor(colorPrimary);
+            }
+        }
+
+        int colorBackground = a.getColor(
+                com.android.internal.R.styleable.ActivityTaskDescription_colorBackground, 0);
+        if (colorBackground != 0 && Color.alpha(colorBackground) == 0xFF) {
+            mTaskDescription.setBackgroundColor(colorBackground);
+        }
+
+        final int statusBarColor = a.getColor(
+                com.android.internal.R.styleable.ActivityTaskDescription_statusBarColor, 0);
+        if (statusBarColor != 0) {
+            mTaskDescription.setStatusBarColor(statusBarColor);
+        }
+
+        final int navigationBarColor = a.getColor(
+                com.android.internal.R.styleable.ActivityTaskDescription_navigationBarColor, 0);
+        if (navigationBarColor != 0) {
+            mTaskDescription.setNavigationBarColor(navigationBarColor);
+        }
+
+        final int targetSdk = getApplicationInfo().targetSdkVersion;
+        final boolean targetPreQ = targetSdk < Build.VERSION_CODES.Q;
+        if (!targetPreQ) {
+            mTaskDescription.setEnsureStatusBarContrastWhenTransparent(a.getBoolean(
+                    R.styleable.ActivityTaskDescription_enforceStatusBarContrast,
+                    false));
+            mTaskDescription.setEnsureNavigationBarContrastWhenTransparent(a.getBoolean(
+                    R.styleable
+                            .ActivityTaskDescription_enforceNavigationBarContrast,
+                    true));
+        }
+
+        a.recycle();
+        setTaskDescription(mTaskDescription);
+    }
+
+    /**
+     * Requests permissions to be granted to this application. These permissions
+     * must be requested in your manifest, they should not be granted to your app,
+     * and they should have protection level {@link
+     * android.content.pm.PermissionInfo#PROTECTION_DANGEROUS dangerous}, regardless
+     * whether they are declared by the platform or a third-party app.
+     * <p>
+     * Normal permissions {@link android.content.pm.PermissionInfo#PROTECTION_NORMAL}
+     * are granted at install time if requested in the manifest. Signature permissions
+     * {@link android.content.pm.PermissionInfo#PROTECTION_SIGNATURE} are granted at
+     * install time if requested in the manifest and the signature of your app matches
+     * the signature of the app declaring the permissions.
+     * </p>
+     * <p>
+     * If your app does not have the requested permissions the user will be presented
+     * with UI for accepting them. After the user has accepted or rejected the
+     * requested permissions you will receive a callback on {@link
+     * #onRequestPermissionsResult(int, String[], int[])} reporting whether the
+     * permissions were granted or not.
+     * </p>
+     * <p>
+     * Note that requesting a permission does not guarantee it will be granted and
+     * your app should be able to run without having this permission.
+     * </p>
+     * <p>
+     * This method may start an activity allowing the user to choose which permissions
+     * to grant and which to reject. Hence, you should be prepared that your activity
+     * may be paused and resumed. Further, granting some permissions may require
+     * a restart of you application. In such a case, the system will recreate the
+     * activity stack before delivering the result to {@link
+     * #onRequestPermissionsResult(int, String[], int[])}.
+     * </p>
+     * <p>
+     * When checking whether you have a permission you should use {@link
+     * #checkSelfPermission(String)}.
+     * </p>
+     * <p>
+     * Calling this API for permissions already granted to your app would show UI
+     * to the user to decide whether the app can still hold these permissions. This
+     * can be useful if the way your app uses data guarded by the permissions
+     * changes significantly.
+     * </p>
+     * <p>
+     * You cannot request a permission if your activity sets {@link
+     * android.R.styleable#AndroidManifestActivity_noHistory noHistory} to
+     * <code>true</code> because in this case the activity would not receive
+     * result callbacks including {@link #onRequestPermissionsResult(int, String[], int[])}.
+     * </p>
+     * <p>
+     * The <a href="https://github.com/googlesamples/android-RuntimePermissions">
+     * RuntimePermissions</a> sample app demonstrates how to use this method to
+     * request permissions at run time.
+     * </p>
+     *
+     * @param permissions The requested permissions. Must me non-null and not empty.
+     * @param requestCode Application specific request code to match with a result
+     *    reported to {@link #onRequestPermissionsResult(int, String[], int[])}.
+     *    Should be >= 0.
+     *
+     * @throws IllegalArgumentException if requestCode is negative.
+     *
+     * @see #onRequestPermissionsResult(int, String[], int[])
+     * @see #checkSelfPermission(String)
+     * @see #shouldShowRequestPermissionRationale(String)
+     */
+    public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
+        if (requestCode < 0) {
+            throw new IllegalArgumentException("requestCode should be >= 0");
+        }
+        if (mHasCurrentPermissionsRequest) {
+            Log.w(TAG, "Can request only one set of permissions at a time");
+            // Dispatch the callback with empty arrays which means a cancellation.
+            onRequestPermissionsResult(requestCode, new String[0], new int[0]);
+            return;
+        }
+        Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
+        startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
+        mHasCurrentPermissionsRequest = true;
+    }
+
+    /**
+     * Callback for the result from requesting permissions. This method
+     * is invoked for every call on {@link #requestPermissions(String[], int)}.
+     * <p>
+     * <strong>Note:</strong> It is possible that the permissions request interaction
+     * with the user is interrupted. In this case you will receive empty permissions
+     * and results arrays which should be treated as a cancellation.
+     * </p>
+     *
+     * @param requestCode The request code passed in {@link #requestPermissions(String[], int)}.
+     * @param permissions The requested permissions. Never null.
+     * @param grantResults The grant results for the corresponding permissions
+     *     which is either {@link android.content.pm.PackageManager#PERMISSION_GRANTED}
+     *     or {@link android.content.pm.PackageManager#PERMISSION_DENIED}. Never null.
+     *
+     * @see #requestPermissions(String[], int)
+     */
+    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
+            @NonNull int[] grantResults) {
+        /* callback - no nothing */
+    }
+
+    /**
+     * Gets whether you should show UI with rationale for requesting a permission.
+     * You should do this only if you do not have the permission and the context in
+     * which the permission is requested does not clearly communicate to the user
+     * what would be the benefit from granting this permission.
+     * <p>
+     * For example, if you write a camera app, requesting the camera permission
+     * would be expected by the user and no rationale for why it is requested is
+     * needed. If however, the app needs location for tagging photos then a non-tech
+     * savvy user may wonder how location is related to taking photos. In this case
+     * you may choose to show UI with rationale of requesting this permission.
+     * </p>
+     *
+     * @param permission A permission your app wants to request.
+     * @return Whether you can show permission rationale UI.
+     *
+     * @see #checkSelfPermission(String)
+     * @see #requestPermissions(String[], int)
+     * @see #onRequestPermissionsResult(int, String[], int[])
+     */
+    public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
+        return getPackageManager().shouldShowRequestPermissionRationale(permission);
+    }
+
+    /**
+     * Same as calling {@link #startActivityForResult(Intent, int, Bundle)}
+     * with no options.
+     *
+     * @param intent The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see #startActivity
+     */
+    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
+        startActivityForResult(intent, requestCode, null);
+    }
+
+    /**
+     * Launch an activity for which you would like a result when it finished.
+     * When this activity exits, your
+     * onActivityResult() method will be called with the given requestCode.
+     * Using a negative requestCode is the same as calling
+     * {@link #startActivity} (the activity is not launched as a sub-activity).
+     *
+     * <p>Note that this method should only be used with Intent protocols
+     * that are defined to return a result.  In other protocols (such as
+     * {@link Intent#ACTION_MAIN} or {@link Intent#ACTION_VIEW}), you may
+     * not get the result when you expect.  For example, if the activity you
+     * are launching uses {@link Intent#FLAG_ACTIVITY_NEW_TASK}, it will not
+     * run in your task and thus you will immediately receive a cancel result.
+     *
+     * <p>As a special case, if you call startActivityForResult() with a requestCode
+     * >= 0 during the initial onCreate(Bundle savedInstanceState)/onResume() of your
+     * activity, then your window will not be displayed until a result is
+     * returned back from the started activity.  This is to avoid visible
+     * flickering when redirecting to another activity.
+     *
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     *
+     * @param intent The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param options Additional options for how the Activity should be started.
+     * See {@link android.content.Context#startActivity(Intent, Bundle)}
+     * Context.startActivity(Intent, Bundle)} for more details.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see #startActivity
+     */
+    public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
+            @Nullable Bundle options) {
+        if (mParent == null) {
+            options = transferSpringboardActivityOptions(options);
+            Instrumentation.ActivityResult ar =
+                mInstrumentation.execStartActivity(
+                    this, mMainThread.getApplicationThread(), mToken, this,
+                    intent, requestCode, options);
+            if (ar != null) {
+                mMainThread.sendActivityResult(
+                    mToken, mEmbeddedID, requestCode, ar.getResultCode(),
+                    ar.getResultData());
+            }
+            if (requestCode >= 0) {
+                // If this start is requesting a result, we can avoid making
+                // the activity visible until the result is received.  Setting
+                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+                // activity hidden during this time, to avoid flickering.
+                // This can only be done when a result is requested because
+                // that guarantees we will get information back when the
+                // activity is finished, no matter what happens to it.
+                mStartedActivity = true;
+            }
+
+            cancelInputsAndStartExitTransition(options);
+            // TODO Consider clearing/flushing other event sources and events for child windows.
+        } else {
+            if (options != null) {
+                mParent.startActivityFromChild(this, intent, requestCode, options);
+            } else {
+                // Note we want to go through this method for compatibility with
+                // existing applications that may have overridden it.
+                mParent.startActivityFromChild(this, intent, requestCode);
+            }
+        }
+    }
+
+    /**
+     * Cancels pending inputs and if an Activity Transition is to be run, starts the transition.
+     *
+     * @param options The ActivityOptions bundle used to start an Activity.
+     */
+    private void cancelInputsAndStartExitTransition(Bundle options) {
+        final View decor = mWindow != null ? mWindow.peekDecorView() : null;
+        if (decor != null) {
+            decor.cancelPendingInputEvents();
+        }
+        if (options != null) {
+            mActivityTransitionState.startExitOutTransition(this, options);
+        }
+    }
+
+    /**
+     * Returns whether there are any activity transitions currently running on this
+     * activity. A return value of {@code true} can mean that either an enter or
+     * exit transition is running, including whether the background of the activity
+     * is animating as a part of that transition.
+     *
+     * @return true if a transition is currently running on this activity, false otherwise.
+     */
+    public boolean isActivityTransitionRunning() {
+        return mActivityTransitionState.isTransitionRunning();
+    }
+
+    private Bundle transferSpringboardActivityOptions(Bundle options) {
+        if (options == null && (mWindow != null && !mWindow.isActive())) {
+            final ActivityOptions activityOptions = getActivityOptions();
+            if (activityOptions != null &&
+                    activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+                return activityOptions.toBundle();
+            }
+        }
+        return options;
+    }
+
+    /**
+     * @hide Implement to provide correct calling token.
+     */
+    @UnsupportedAppUsage
+    public void startActivityForResultAsUser(Intent intent, int requestCode, UserHandle user) {
+        startActivityForResultAsUser(intent, requestCode, null, user);
+    }
+
+    /**
+     * @hide Implement to provide correct calling token.
+     */
+    public void startActivityForResultAsUser(Intent intent, int requestCode,
+            @Nullable Bundle options, UserHandle user) {
+        startActivityForResultAsUser(intent, mEmbeddedID, requestCode, options, user);
+    }
+
+    /**
+     * @hide Implement to provide correct calling token.
+     */
+    public void startActivityForResultAsUser(Intent intent, String resultWho, int requestCode,
+            @Nullable Bundle options, UserHandle user) {
+        if (mParent != null) {
+            throw new RuntimeException("Can't be called from a child");
+        }
+        options = transferSpringboardActivityOptions(options);
+        Instrumentation.ActivityResult ar = mInstrumentation.execStartActivity(
+                this, mMainThread.getApplicationThread(), mToken, resultWho, intent, requestCode,
+                options, user);
+        if (ar != null) {
+            mMainThread.sendActivityResult(
+                mToken, mEmbeddedID, requestCode, ar.getResultCode(), ar.getResultData());
+        }
+        if (requestCode >= 0) {
+            // If this start is requesting a result, we can avoid making
+            // the activity visible until the result is received.  Setting
+            // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+            // activity hidden during this time, to avoid flickering.
+            // This can only be done when a result is requested because
+            // that guarantees we will get information back when the
+            // activity is finished, no matter what happens to it.
+            mStartedActivity = true;
+        }
+
+        cancelInputsAndStartExitTransition(options);
+    }
+
+    /**
+     * @hide Implement to provide correct calling token.
+     */
+    @Override
+    public void startActivityAsUser(Intent intent, UserHandle user) {
+        startActivityAsUser(intent, null, user);
+    }
+
+    /**
+     * @hide Implement to provide correct calling token.
+     */
+    public void startActivityAsUser(Intent intent, Bundle options, UserHandle user) {
+        if (mParent != null) {
+            throw new RuntimeException("Can't be called from a child");
+        }
+        options = transferSpringboardActivityOptions(options);
+        Instrumentation.ActivityResult ar =
+                mInstrumentation.execStartActivity(
+                        this, mMainThread.getApplicationThread(), mToken, mEmbeddedID,
+                        intent, -1, options, user);
+        if (ar != null) {
+            mMainThread.sendActivityResult(
+                mToken, mEmbeddedID, -1, ar.getResultCode(),
+                ar.getResultData());
+        }
+        cancelInputsAndStartExitTransition(options);
+    }
+
+    /**
+     * Start a new activity as if it was started by the activity that started our
+     * current activity.  This is for the resolver and chooser activities, which operate
+     * as intermediaries that dispatch their intent to the target the user selects -- to
+     * do this, they must perform all security checks including permission grants as if
+     * their launch had come from the original activity.
+     * @param intent The Intent to start.
+     * @param options ActivityOptions or null.
+     * @param permissionToken Token received from the system that permits this call to be made.
+     * @param ignoreTargetSecurity If true, the activity manager will not check whether the
+     * caller it is doing the start is, is actually allowed to start the target activity.
+     * If you set this to true, you must set an explicit component in the Intent and do any
+     * appropriate security checks yourself.
+     * @param userId The user the new activity should run as.
+     * @hide
+     */
+    public void startActivityAsCaller(Intent intent, @Nullable Bundle options,
+            IBinder permissionToken, boolean ignoreTargetSecurity, int userId) {
+        if (mParent != null) {
+            throw new RuntimeException("Can't be called from a child");
+        }
+        options = transferSpringboardActivityOptions(options);
+        Instrumentation.ActivityResult ar =
+                mInstrumentation.execStartActivityAsCaller(
+                        this, mMainThread.getApplicationThread(), mToken, this,
+                        intent, -1, options, permissionToken, ignoreTargetSecurity, userId);
+        if (ar != null) {
+            mMainThread.sendActivityResult(
+                mToken, mEmbeddedID, -1, ar.getResultCode(),
+                ar.getResultData());
+        }
+        cancelInputsAndStartExitTransition(options);
+    }
+
+    /**
+     * Same as calling {@link #startIntentSenderForResult(IntentSender, int,
+     * Intent, int, int, int, Bundle)} with no options.
+     *
+     * @param intent The IntentSender to launch.
+     * @param requestCode If >= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param fillInIntent If non-null, this will be provided as the
+     * intent parameter to {@link IntentSender#sendIntent}.
+     * @param flagsMask Intent flags in the original IntentSender that you
+     * would like to change.
+     * @param flagsValues Desired values for any bits set in
+     * <var>flagsMask</var>
+     * @param extraFlags Always set to 0.
+     */
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+            throws IntentSender.SendIntentException {
+        startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
+                flagsValues, extraFlags, null);
+    }
+
+    /**
+     * Like {@link #startActivityForResult(Intent, int)}, but allowing you
+     * to use a IntentSender to describe the activity to be started.  If
+     * the IntentSender is for an activity, that activity will be started
+     * as if you had called the regular {@link #startActivityForResult(Intent, int)}
+     * here; otherwise, its associated action will be executed (such as
+     * sending a broadcast) as if you had called
+     * {@link IntentSender#sendIntent IntentSender.sendIntent} on it.
+     *
+     * @param intent The IntentSender to launch.
+     * @param requestCode If >= 0, this code will be returned in
+     *                    onActivityResult() when the activity exits.
+     * @param fillInIntent If non-null, this will be provided as the
+     * intent parameter to {@link IntentSender#sendIntent}.
+     * @param flagsMask Intent flags in the original IntentSender that you
+     * would like to change.
+     * @param flagsValues Desired values for any bits set in
+     * <var>flagsMask</var>
+     * @param extraFlags Always set to 0.
+     * @param options Additional options for how the Activity should be started.
+     * See {@link android.content.Context#startActivity(Intent, Bundle)}
+     * Context.startActivity(Intent, Bundle)} for more details.  If options
+     * have also been supplied by the IntentSender, options given here will
+     * override any that conflict with those given by the IntentSender.
+     */
+    public void startIntentSenderForResult(IntentSender intent, int requestCode,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) throws IntentSender.SendIntentException {
+        if (mParent == null) {
+            startIntentSenderForResultInner(intent, mEmbeddedID, requestCode, fillInIntent,
+                    flagsMask, flagsValues, options);
+        } else if (options != null) {
+            mParent.startIntentSenderFromChild(this, intent, requestCode,
+                    fillInIntent, flagsMask, flagsValues, extraFlags, options);
+        } else {
+            // Note we want to go through this call for compatibility with
+            // existing applications that may have overridden the method.
+            mParent.startIntentSenderFromChild(this, intent, requestCode,
+                    fillInIntent, flagsMask, flagsValues, extraFlags);
+        }
+    }
+
+    private void startIntentSenderForResultInner(IntentSender intent, String who, int requestCode,
+            Intent fillInIntent, int flagsMask, int flagsValues,
+            Bundle options)
+            throws IntentSender.SendIntentException {
+        try {
+            options = transferSpringboardActivityOptions(options);
+            String resolvedType = null;
+            if (fillInIntent != null) {
+                fillInIntent.migrateExtraStreamToClipData();
+                fillInIntent.prepareToLeaveProcess(this);
+                resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver());
+            }
+            int result = ActivityTaskManager.getService()
+                .startActivityIntentSender(mMainThread.getApplicationThread(),
+                        intent != null ? intent.getTarget() : null,
+                        intent != null ? intent.getWhitelistToken() : null,
+                        fillInIntent, resolvedType, mToken, who,
+                        requestCode, flagsMask, flagsValues, options);
+            if (result == ActivityManager.START_CANCELED) {
+                throw new IntentSender.SendIntentException();
+            }
+            Instrumentation.checkStartActivityResult(result, null);
+
+            if (options != null) {
+                // Only when the options are not null, as the intent can point to something other
+                // than an Activity.
+                cancelInputsAndStartExitTransition(options);
+            }
+        } catch (RemoteException e) {
+        }
+        if (requestCode >= 0) {
+            // If this start is requesting a result, we can avoid making
+            // the activity visible until the result is received.  Setting
+            // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+            // activity hidden during this time, to avoid flickering.
+            // This can only be done when a result is requested because
+            // that guarantees we will get information back when the
+            // activity is finished, no matter what happens to it.
+            mStartedActivity = true;
+        }
+    }
+
+    /**
+     * Same as {@link #startActivity(Intent, Bundle)} with no options
+     * specified.
+     *
+     * @param intent The intent to start.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see #startActivity(Intent, Bundle)
+     * @see #startActivityForResult
+     */
+    @Override
+    public void startActivity(Intent intent) {
+        this.startActivity(intent, null);
+    }
+
+    /**
+     * Launch a new activity.  You will not receive any information about when
+     * the activity exits.  This implementation overrides the base version,
+     * providing information about
+     * the activity performing the launch.  Because of this additional
+     * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not
+     * required; if not specified, the new activity will be added to the
+     * task of the caller.
+     *
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     *
+     * @param intent The intent to start.
+     * @param options Additional options for how the Activity should be started.
+     * See {@link android.content.Context#startActivity(Intent, Bundle)}
+     * Context.startActivity(Intent, Bundle)} for more details.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see #startActivity(Intent)
+     * @see #startActivityForResult
+     */
+    @Override
+    public void startActivity(Intent intent, @Nullable Bundle options) {
+        if (options != null) {
+            startActivityForResult(intent, -1, options);
+        } else {
+            // Note we want to go through this call for compatibility with
+            // applications that may have overridden the method.
+            startActivityForResult(intent, -1);
+        }
+    }
+
+    /**
+     * Same as {@link #startActivities(Intent[], Bundle)} with no options
+     * specified.
+     *
+     * @param intents The intents to start.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see #startActivities(Intent[], Bundle)
+     * @see #startActivityForResult
+     */
+    @Override
+    public void startActivities(Intent[] intents) {
+        startActivities(intents, null);
+    }
+
+    /**
+     * Launch a new activity.  You will not receive any information about when
+     * the activity exits.  This implementation overrides the base version,
+     * providing information about
+     * the activity performing the launch.  Because of this additional
+     * information, the {@link Intent#FLAG_ACTIVITY_NEW_TASK} launch flag is not
+     * required; if not specified, the new activity will be added to the
+     * task of the caller.
+     *
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     *
+     * @param intents The intents to start.
+     * @param options Additional options for how the Activity should be started.
+     * See {@link android.content.Context#startActivity(Intent, Bundle)}
+     * Context.startActivity(Intent, Bundle)} for more details.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see #startActivities(Intent[])
+     * @see #startActivityForResult
+     */
+    @Override
+    public void startActivities(Intent[] intents, @Nullable Bundle options) {
+        mInstrumentation.execStartActivities(this, mMainThread.getApplicationThread(),
+                mToken, this, intents, options);
+    }
+
+    /**
+     * Same as calling {@link #startIntentSender(IntentSender, Intent, int, int, int, Bundle)}
+     * with no options.
+     *
+     * @param intent The IntentSender to launch.
+     * @param fillInIntent If non-null, this will be provided as the
+     * intent parameter to {@link IntentSender#sendIntent}.
+     * @param flagsMask Intent flags in the original IntentSender that you
+     * would like to change.
+     * @param flagsValues Desired values for any bits set in
+     * <var>flagsMask</var>
+     * @param extraFlags Always set to 0.
+     */
+    public void startIntentSender(IntentSender intent,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+            throws IntentSender.SendIntentException {
+        startIntentSender(intent, fillInIntent, flagsMask, flagsValues,
+                extraFlags, null);
+    }
+
+    /**
+     * Like {@link #startActivity(Intent, Bundle)}, but taking a IntentSender
+     * to start; see
+     * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int, Bundle)}
+     * for more information.
+     *
+     * @param intent The IntentSender to launch.
+     * @param fillInIntent If non-null, this will be provided as the
+     * intent parameter to {@link IntentSender#sendIntent}.
+     * @param flagsMask Intent flags in the original IntentSender that you
+     * would like to change.
+     * @param flagsValues Desired values for any bits set in
+     * <var>flagsMask</var>
+     * @param extraFlags Always set to 0.
+     * @param options Additional options for how the Activity should be started.
+     * See {@link android.content.Context#startActivity(Intent, Bundle)}
+     * Context.startActivity(Intent, Bundle)} for more details.  If options
+     * have also been supplied by the IntentSender, options given here will
+     * override any that conflict with those given by the IntentSender.
+     */
+    public void startIntentSender(IntentSender intent,
+            @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+            Bundle options) throws IntentSender.SendIntentException {
+        if (options != null) {
+            startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
+                    flagsValues, extraFlags, options);
+        } else {
+            // Note we want to go through this call for compatibility with
+            // applications that may have overridden the method.
+            startIntentSenderForResult(intent, -1, fillInIntent, flagsMask,
+                    flagsValues, extraFlags);
+        }
+    }
+
+    /**
+     * Same as calling {@link #startActivityIfNeeded(Intent, int, Bundle)}
+     * with no options.
+     *
+     * @param intent The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *         onActivityResult() when the activity exits, as described in
+     *         {@link #startActivityForResult}.
+     *
+     * @return If a new activity was launched then true is returned; otherwise
+     *         false is returned and you must handle the Intent yourself.
+     *
+     * @see #startActivity
+     * @see #startActivityForResult
+     */
+    public boolean startActivityIfNeeded(@RequiresPermission @NonNull Intent intent,
+            int requestCode) {
+        return startActivityIfNeeded(intent, requestCode, null);
+    }
+
+    /**
+     * A special variation to launch an activity only if a new activity
+     * instance is needed to handle the given Intent.  In other words, this is
+     * just like {@link #startActivityForResult(Intent, int)} except: if you are
+     * using the {@link Intent#FLAG_ACTIVITY_SINGLE_TOP} flag, or
+     * singleTask or singleTop
+     * {@link android.R.styleable#AndroidManifestActivity_launchMode launchMode},
+     * and the activity
+     * that handles <var>intent</var> is the same as your currently running
+     * activity, then a new instance is not needed.  In this case, instead of
+     * the normal behavior of calling {@link #onNewIntent} this function will
+     * return and you can handle the Intent yourself.
+     *
+     * <p>This function can only be called from a top-level activity; if it is
+     * called from a child activity, a runtime exception will be thrown.
+     *
+     * @param intent The intent to start.
+     * @param requestCode If >= 0, this code will be returned in
+     *         onActivityResult() when the activity exits, as described in
+     *         {@link #startActivityForResult}.
+     * @param options Additional options for how the Activity should be started.
+     * See {@link android.content.Context#startActivity(Intent, Bundle)}
+     * Context.startActivity(Intent, Bundle)} for more details.
+     *
+     * @return If a new activity was launched then true is returned; otherwise
+     *         false is returned and you must handle the Intent yourself.
+     *
+     * @see #startActivity
+     * @see #startActivityForResult
+     */
+    public boolean startActivityIfNeeded(@RequiresPermission @NonNull Intent intent,
+            int requestCode, @Nullable Bundle options) {
+        if (mParent == null) {
+            int result = ActivityManager.START_RETURN_INTENT_TO_CALLER;
+            try {
+                Uri referrer = onProvideReferrer();
+                if (referrer != null) {
+                    intent.putExtra(Intent.EXTRA_REFERRER, referrer);
+                }
+                intent.migrateExtraStreamToClipData();
+                intent.prepareToLeaveProcess(this);
+                result = ActivityTaskManager.getService()
+                    .startActivity(mMainThread.getApplicationThread(), getBasePackageName(),
+                            intent, intent.resolveTypeIfNeeded(getContentResolver()), mToken,
+                            mEmbeddedID, requestCode, ActivityManager.START_FLAG_ONLY_IF_NEEDED,
+                            null, options);
+            } catch (RemoteException e) {
+                // Empty
+            }
+
+            Instrumentation.checkStartActivityResult(result, intent);
+
+            if (requestCode >= 0) {
+                // If this start is requesting a result, we can avoid making
+                // the activity visible until the result is received.  Setting
+                // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
+                // activity hidden during this time, to avoid flickering.
+                // This can only be done when a result is requested because
+                // that guarantees we will get information back when the
+                // activity is finished, no matter what happens to it.
+                mStartedActivity = true;
+            }
+            return result != ActivityManager.START_RETURN_INTENT_TO_CALLER;
+        }
+
+        throw new UnsupportedOperationException(
+            "startActivityIfNeeded can only be called from a top-level activity");
+    }
+
+    /**
+     * Same as calling {@link #startNextMatchingActivity(Intent, Bundle)} with
+     * no options.
+     *
+     * @param intent The intent to dispatch to the next activity.  For
+     * correct behavior, this must be the same as the Intent that started
+     * your own activity; the only changes you can make are to the extras
+     * inside of it.
+     *
+     * @return Returns a boolean indicating whether there was another Activity
+     * to start: true if there was a next activity to start, false if there
+     * wasn't.  In general, if true is returned you will then want to call
+     * finish() on yourself.
+     */
+    public boolean startNextMatchingActivity(@RequiresPermission @NonNull Intent intent) {
+        return startNextMatchingActivity(intent, null);
+    }
+
+    /**
+     * Special version of starting an activity, for use when you are replacing
+     * other activity components.  You can use this to hand the Intent off
+     * to the next Activity that can handle it.  You typically call this in
+     * {@link #onCreate} with the Intent returned by {@link #getIntent}.
+     *
+     * @param intent The intent to dispatch to the next activity.  For
+     * correct behavior, this must be the same as the Intent that started
+     * your own activity; the only changes you can make are to the extras
+     * inside of it.
+     * @param options Additional options for how the Activity should be started.
+     * See {@link android.content.Context#startActivity(Intent, Bundle)}
+     * Context.startActivity(Intent, Bundle)} for more details.
+     *
+     * @return Returns a boolean indicating whether there was another Activity
+     * to start: true if there was a next activity to start, false if there
+     * wasn't.  In general, if true is returned you will then want to call
+     * finish() on yourself.
+     */
+    public boolean startNextMatchingActivity(@RequiresPermission @NonNull Intent intent,
+            @Nullable Bundle options) {
+        if (mParent == null) {
+            try {
+                intent.migrateExtraStreamToClipData();
+                intent.prepareToLeaveProcess(this);
+                return ActivityTaskManager.getService()
+                    .startNextMatchingActivity(mToken, intent, options);
+            } catch (RemoteException e) {
+                // Empty
+            }
+            return false;
+        }
+
+        throw new UnsupportedOperationException(
+            "startNextMatchingActivity can only be called from a top-level activity");
+    }
+
+    /**
+     * Same as calling {@link #startActivityFromChild(Activity, Intent, int, Bundle)}
+     * with no options.
+     *
+     * @param child The activity making the call.
+     * @param intent The intent to start.
+     * @param requestCode Reply request code.  < 0 if reply is not requested.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see #startActivity
+     * @see #startActivityForResult
+     */
+    public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
+            int requestCode) {
+        startActivityFromChild(child, intent, requestCode, null);
+    }
+
+    /**
+     * This is called when a child activity of this one calls its
+     * {@link #startActivity} or {@link #startActivityForResult} method.
+     *
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     *
+     * @param child The activity making the call.
+     * @param intent The intent to start.
+     * @param requestCode Reply request code.  < 0 if reply is not requested.
+     * @param options Additional options for how the Activity should be started.
+     * See {@link android.content.Context#startActivity(Intent, Bundle)}
+     * Context.startActivity(Intent, Bundle)} for more details.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see #startActivity
+     * @see #startActivityForResult
+     */
+    public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
+            int requestCode, @Nullable Bundle options) {
+        options = transferSpringboardActivityOptions(options);
+        Instrumentation.ActivityResult ar =
+            mInstrumentation.execStartActivity(
+                this, mMainThread.getApplicationThread(), mToken, child,
+                intent, requestCode, options);
+        if (ar != null) {
+            mMainThread.sendActivityResult(
+                mToken, child.mEmbeddedID, requestCode,
+                ar.getResultCode(), ar.getResultData());
+        }
+        cancelInputsAndStartExitTransition(options);
+    }
+
+    /**
+     * Same as calling {@link #startActivityFromFragment(Fragment, Intent, int, Bundle)}
+     * with no options.
+     *
+     * @param fragment The fragment making the call.
+     * @param intent The intent to start.
+     * @param requestCode Reply request code.  < 0 if reply is not requested.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see Fragment#startActivity
+     * @see Fragment#startActivityForResult
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
+     * android.support.v4.app.Fragment,Intent,int)}
+     */
+    @Deprecated
+    public void startActivityFromFragment(@NonNull Fragment fragment,
+            @RequiresPermission Intent intent, int requestCode) {
+        startActivityFromFragment(fragment, intent, requestCode, null);
+    }
+
+    /**
+     * This is called when a Fragment in this activity calls its
+     * {@link Fragment#startActivity} or {@link Fragment#startActivityForResult}
+     * method.
+     *
+     * <p>This method throws {@link android.content.ActivityNotFoundException}
+     * if there was no Activity found to run the given Intent.
+     *
+     * @param fragment The fragment making the call.
+     * @param intent The intent to start.
+     * @param requestCode Reply request code.  < 0 if reply is not requested.
+     * @param options Additional options for how the Activity should be started.
+     * See {@link android.content.Context#startActivity(Intent, Bundle)}
+     * Context.startActivity(Intent, Bundle)} for more details.
+     *
+     * @throws android.content.ActivityNotFoundException
+     *
+     * @see Fragment#startActivity
+     * @see Fragment#startActivityForResult
+     *
+     * @deprecated Use {@link android.support.v4.app.FragmentActivity#startActivityFromFragment(
+     * android.support.v4.app.Fragment,Intent,int,Bundle)}
+     */
+    @Deprecated
+    public void startActivityFromFragment(@NonNull Fragment fragment,
+            @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options) {
+        startActivityForResult(fragment.mWho, intent, requestCode, options);
+    }
+
+    /**
+     * @hide
+     */
+    public void startActivityAsUserFromFragment(@NonNull Fragment fragment,
+            @RequiresPermission Intent intent, int requestCode, @Nullable Bundle options,
+            UserHandle user) {
+        startActivityForResultAsUser(intent, fragment.mWho, requestCode, options, user);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    @UnsupportedAppUsage
+    public void startActivityForResult(
+            String who, Intent intent, int requestCode, @Nullable Bundle options) {
+        Uri referrer = onProvideReferrer();
+        if (referrer != null) {
+            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
+        }
+        options = transferSpringboardActivityOptions(options);
+        Instrumentation.ActivityResult ar =
+            mInstrumentation.execStartActivity(
+                this, mMainThread.getApplicationThread(), mToken, who,
+                intent, requestCode, options);
+        if (ar != null) {
+            mMainThread.sendActivityResult(
+                mToken, who, requestCode,
+                ar.getResultCode(), ar.getResultData());
+        }
+        cancelInputsAndStartExitTransition(options);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public boolean canStartActivityForResult() {
+        return true;
+    }
+
+    /**
+     * Same as calling {@link #startIntentSenderFromChild(Activity, IntentSender,
+     * int, Intent, int, int, int, Bundle)} with no options.
+     */
+    public void startIntentSenderFromChild(Activity child, IntentSender intent,
+            int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
+            int extraFlags)
+            throws IntentSender.SendIntentException {
+        startIntentSenderFromChild(child, intent, requestCode, fillInIntent,
+                flagsMask, flagsValues, extraFlags, null);
+    }
+
+    /**
+     * Like {@link #startActivityFromChild(Activity, Intent, int)}, but
+     * taking a IntentSender; see
+     * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}
+     * for more information.
+     */
+    public void startIntentSenderFromChild(Activity child, IntentSender intent,
+            int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
+            int extraFlags, @Nullable Bundle options)
+            throws IntentSender.SendIntentException {
+        startIntentSenderForResultInner(intent, child.mEmbeddedID, requestCode, fillInIntent,
+                flagsMask, flagsValues, options);
+    }
+
+    /**
+     * Like {@link #startIntentSenderFromChild}, but taking a Fragment; see
+     * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)}
+     * for more information.
+     *
+     * @hide
+     */
+    public void startIntentSenderFromChildFragment(Fragment child, IntentSender intent,
+            int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
+            int extraFlags, @Nullable Bundle options)
+            throws IntentSender.SendIntentException {
+        startIntentSenderForResultInner(intent, child.mWho, requestCode, fillInIntent,
+                flagsMask, flagsValues, options);
+    }
+
+    /**
+     * Call immediately after one of the flavors of {@link #startActivity(Intent)}
+     * or {@link #finish} to specify an explicit transition animation to
+     * perform next.
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#JELLY_BEAN} an alternative
+     * to using this with starting activities is to supply the desired animation
+     * information through a {@link ActivityOptions} bundle to
+     * {@link #startActivity(Intent, Bundle)} or a related function.  This allows
+     * you to specify a custom animation even when starting an activity from
+     * outside the context of the current top activity.
+     *
+     * @param enterAnim A resource ID of the animation resource to use for
+     * the incoming activity.  Use 0 for no animation.
+     * @param exitAnim A resource ID of the animation resource to use for
+     * the outgoing activity.  Use 0 for no animation.
+     */
+    public void overridePendingTransition(int enterAnim, int exitAnim) {
+        try {
+            ActivityTaskManager.getService().overridePendingTransition(
+                    mToken, getPackageName(), enterAnim, exitAnim);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Call this to set the result that your activity will return to its
+     * caller.
+     *
+     * @param resultCode The result code to propagate back to the originating
+     *                   activity, often RESULT_CANCELED or RESULT_OK
+     *
+     * @see #RESULT_CANCELED
+     * @see #RESULT_OK
+     * @see #RESULT_FIRST_USER
+     * @see #setResult(int, Intent)
+     */
+    public final void setResult(int resultCode) {
+        synchronized (this) {
+            mResultCode = resultCode;
+            mResultData = null;
+        }
+    }
+
+    /**
+     * Call this to set the result that your activity will return to its
+     * caller.
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, the Intent
+     * you supply here can have {@link Intent#FLAG_GRANT_READ_URI_PERMISSION
+     * Intent.FLAG_GRANT_READ_URI_PERMISSION} and/or {@link Intent#FLAG_GRANT_WRITE_URI_PERMISSION
+     * Intent.FLAG_GRANT_WRITE_URI_PERMISSION} set.  This will grant the
+     * Activity receiving the result access to the specific URIs in the Intent.
+     * Access will remain until the Activity has finished (it will remain across the hosting
+     * process being killed and other temporary destruction) and will be added
+     * to any existing set of URI permissions it already holds.
+     *
+     * @param resultCode The result code to propagate back to the originating
+     *                   activity, often RESULT_CANCELED or RESULT_OK
+     * @param data The data to propagate back to the originating activity.
+     *
+     * @see #RESULT_CANCELED
+     * @see #RESULT_OK
+     * @see #RESULT_FIRST_USER
+     * @see #setResult(int)
+     */
+    public final void setResult(int resultCode, Intent data) {
+        synchronized (this) {
+            mResultCode = resultCode;
+            mResultData = data;
+        }
+    }
+
+    /**
+     * Return information about who launched this activity.  If the launching Intent
+     * contains an {@link android.content.Intent#EXTRA_REFERRER Intent.EXTRA_REFERRER},
+     * that will be returned as-is; otherwise, if known, an
+     * {@link Intent#URI_ANDROID_APP_SCHEME android-app:} referrer URI containing the
+     * package name that started the Intent will be returned.  This may return null if no
+     * referrer can be identified -- it is neither explicitly specified, nor is it known which
+     * application package was involved.
+     *
+     * <p>If called while inside the handling of {@link #onNewIntent}, this function will
+     * return the referrer that submitted that new intent to the activity.  Otherwise, it
+     * always returns the referrer of the original Intent.</p>
+     *
+     * <p>Note that this is <em>not</em> a security feature -- you can not trust the
+     * referrer information, applications can spoof it.</p>
+     */
+    @Nullable
+    public Uri getReferrer() {
+        Intent intent = getIntent();
+        try {
+            Uri referrer = intent.getParcelableExtra(Intent.EXTRA_REFERRER);
+            if (referrer != null) {
+                return referrer;
+            }
+            String referrerName = intent.getStringExtra(Intent.EXTRA_REFERRER_NAME);
+            if (referrerName != null) {
+                return Uri.parse(referrerName);
+            }
+        } catch (BadParcelableException e) {
+            Log.w(TAG, "Cannot read referrer from intent;"
+                    + " intent extras contain unknown custom Parcelable objects");
+        }
+        if (mReferrer != null) {
+            return new Uri.Builder().scheme("android-app").authority(mReferrer).build();
+        }
+        return null;
+    }
+
+    /**
+     * Override to generate the desired referrer for the content currently being shown
+     * by the app.  The default implementation returns null, meaning the referrer will simply
+     * be the android-app: of the package name of this activity.  Return a non-null Uri to
+     * have that supplied as the {@link Intent#EXTRA_REFERRER} of any activities started from it.
+     */
+    public Uri onProvideReferrer() {
+        return null;
+    }
+
+    /**
+     * Return the name of the package that invoked this activity.  This is who
+     * the data in {@link #setResult setResult()} will be sent to.  You can
+     * use this information to validate that the recipient is allowed to
+     * receive the data.
+     *
+     * <p class="note">Note: if the calling activity is not expecting a result (that is it
+     * did not use the {@link #startActivityForResult}
+     * form that includes a request code), then the calling package will be
+     * null.</p>
+     *
+     * <p class="note">Note: prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2},
+     * the result from this method was unstable.  If the process hosting the calling
+     * package was no longer running, it would return null instead of the proper package
+     * name.  You can use {@link #getCallingActivity()} and retrieve the package name
+     * from that instead.</p>
+     *
+     * @return The package of the activity that will receive your
+     *         reply, or null if none.
+     */
+    @Nullable
+    public String getCallingPackage() {
+        try {
+            return ActivityTaskManager.getService().getCallingPackage(mToken);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Return the name of the activity that invoked this activity.  This is
+     * who the data in {@link #setResult setResult()} will be sent to.  You
+     * can use this information to validate that the recipient is allowed to
+     * receive the data.
+     *
+     * <p class="note">Note: if the calling activity is not expecting a result (that is it
+     * did not use the {@link #startActivityForResult}
+     * form that includes a request code), then the calling package will be
+     * null.
+     *
+     * @return The ComponentName of the activity that will receive your
+     *         reply, or null if none.
+     */
+    @Nullable
+    public ComponentName getCallingActivity() {
+        try {
+            return ActivityTaskManager.getService().getCallingActivity(mToken);
+        } catch (RemoteException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Control whether this activity's main window is visible.  This is intended
+     * only for the special case of an activity that is not going to show a
+     * UI itself, but can't just finish prior to onResume() because it needs
+     * to wait for a service binding or such.  Setting this to false allows
+     * you to prevent your UI from being shown during that time.
+     *
+     * <p>The default value for this is taken from the
+     * {@link android.R.attr#windowNoDisplay} attribute of the activity's theme.
+     */
+    public void setVisible(boolean visible) {
+        if (mVisibleFromClient != visible) {
+            mVisibleFromClient = visible;
+            if (mVisibleFromServer) {
+                if (visible) makeVisible();
+                else mDecor.setVisibility(View.INVISIBLE);
+            }
+        }
+    }
+
+    void makeVisible() {
+        if (!mWindowAdded) {
+            ViewManager wm = getWindowManager();
+            wm.addView(mDecor, getWindow().getAttributes());
+            mWindowAdded = true;
+        }
+        mDecor.setVisibility(View.VISIBLE);
+    }
+
+    /**
+     * Check to see whether this activity is in the process of finishing,
+     * either because you called {@link #finish} on it or someone else
+     * has requested that it finished.  This is often used in
+     * {@link #onPause} to determine whether the activity is simply pausing or
+     * completely finishing.
+     *
+     * @return If the activity is finishing, returns true; else returns false.
+     *
+     * @see #finish
+     */
+    public boolean isFinishing() {
+        return mFinished;
+    }
+
+    /**
+     * Returns true if the final {@link #onDestroy()} call has been made
+     * on the Activity, so this instance is now dead.
+     */
+    public boolean isDestroyed() {
+        return mDestroyed;
+    }
+
+    /**
+     * Check to see whether this activity is in the process of being destroyed in order to be
+     * recreated with a new configuration. This is often used in
+     * {@link #onStop} to determine whether the state needs to be cleaned up or will be passed
+     * on to the next instance of the activity via {@link #onRetainNonConfigurationInstance()}.
+     *
+     * @return If the activity is being torn down in order to be recreated with a new configuration,
+     * returns true; else returns false.
+     */
+    public boolean isChangingConfigurations() {
+        return mChangingConfigurations;
+    }
+
+    /**
+     * Cause this Activity to be recreated with a new instance.  This results
+     * in essentially the same flow as when the Activity is created due to
+     * a configuration change -- the current instance will go through its
+     * lifecycle to {@link #onDestroy} and a new instance then created after it.
+     */
+    public void recreate() {
+        if (mParent != null) {
+            throw new IllegalStateException("Can only be called on top-level activity");
+        }
+        if (Looper.myLooper() != mMainThread.getLooper()) {
+            throw new IllegalStateException("Must be called from main thread");
+        }
+        mMainThread.scheduleRelaunchActivity(mToken);
+    }
+
+    /**
+     * Finishes the current activity and specifies whether to remove the task associated with this
+     * activity.
+     */
+    @UnsupportedAppUsage
+    private void finish(int finishTask) {
+        if (mParent == null) {
+            int resultCode;
+            Intent resultData;
+            synchronized (this) {
+                resultCode = mResultCode;
+                resultData = mResultData;
+            }
+            if (false) Log.v(TAG, "Finishing self: token=" + mToken);
+            try {
+                if (resultData != null) {
+                    resultData.prepareToLeaveProcess(this);
+                }
+                if (ActivityTaskManager.getService()
+                        .finishActivity(mToken, resultCode, resultData, finishTask)) {
+                    mFinished = true;
+                }
+            } catch (RemoteException e) {
+                // Empty
+            }
+        } else {
+            mParent.finishFromChild(this);
+        }
+
+        // Activity was launched when user tapped a link in the Autofill Save UI - Save UI must
+        // be restored now.
+        if (mIntent != null && mIntent.hasExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN)) {
+            getAutofillManager().onPendingSaveUi(AutofillManager.PENDING_UI_OPERATION_RESTORE,
+                    mIntent.getIBinderExtra(AutofillManager.EXTRA_RESTORE_SESSION_TOKEN));
+        }
+    }
+
+    /**
+     * Call this when your activity is done and should be closed.  The
+     * ActivityResult is propagated back to whoever launched you via
+     * onActivityResult().
+     */
+    public void finish() {
+        finish(DONT_FINISH_TASK_WITH_ACTIVITY);
+    }
+
+    /**
+     * Finish this activity as well as all activities immediately below it
+     * in the current task that have the same affinity.  This is typically
+     * used when an application can be launched on to another task (such as
+     * from an ACTION_VIEW of a content type it understands) and the user
+     * has used the up navigation to switch out of the current task and in
+     * to its own task.  In this case, if the user has navigated down into
+     * any other activities of the second application, all of those should
+     * be removed from the original task as part of the task switch.
+     *
+     * <p>Note that this finish does <em>not</em> allow you to deliver results
+     * to the previous activity, and an exception will be thrown if you are trying
+     * to do so.</p>
+     */
+    public void finishAffinity() {
+        if (mParent != null) {
+            throw new IllegalStateException("Can not be called from an embedded activity");
+        }
+        if (mResultCode != RESULT_CANCELED || mResultData != null) {
+            throw new IllegalStateException("Can not be called to deliver a result");
+        }
+        try {
+            if (ActivityTaskManager.getService().finishActivityAffinity(mToken)) {
+                mFinished = true;
+            }
+        } catch (RemoteException e) {
+            // Empty
+        }
+    }
+
+    /**
+     * This is called when a child activity of this one calls its
+     * {@link #finish} method.  The default implementation simply calls
+     * finish() on this activity (the parent), finishing the entire group.
+     *
+     * @param child The activity making the call.
+     *
+     * @see #finish
+     */
+    public void finishFromChild(Activity child) {
+        finish();
+    }
+
+    /**
+     * Reverses the Activity Scene entry Transition and triggers the calling Activity
+     * to reverse its exit Transition. When the exit Transition completes,
+     * {@link #finish()} is called. If no entry Transition was used, finish() is called
+     * immediately and the Activity exit Transition is run.
+     * @see android.app.ActivityOptions#makeSceneTransitionAnimation(Activity, android.util.Pair[])
+     */
+    public void finishAfterTransition() {
+        if (!mActivityTransitionState.startExitBackTransition(this)) {
+            finish();
+        }
+    }
+
+    /**
+     * Force finish another activity that you had previously started with
+     * {@link #startActivityForResult}.
+     *
+     * @param requestCode The request code of the activity that you had
+     *                    given to startActivityForResult().  If there are multiple
+     *                    activities started with this request code, they
+     *                    will all be finished.
+     */
+    public void finishActivity(int requestCode) {
+        if (mParent == null) {
+            try {
+                ActivityTaskManager.getService()
+                    .finishSubActivity(mToken, mEmbeddedID, requestCode);
+            } catch (RemoteException e) {
+                // Empty
+            }
+        } else {
+            mParent.finishActivityFromChild(this, requestCode);
+        }
+    }
+
+    /**
+     * This is called when a child activity of this one calls its
+     * finishActivity().
+     *
+     * @param child The activity making the call.
+     * @param requestCode Request code that had been used to start the
+     *                    activity.
+     */
+    public void finishActivityFromChild(@NonNull Activity child, int requestCode) {
+        try {
+            ActivityTaskManager.getService()
+                .finishSubActivity(mToken, child.mEmbeddedID, requestCode);
+        } catch (RemoteException e) {
+            // Empty
+        }
+    }
+
+    /**
+     * Call this when your activity is done and should be closed and the task should be completely
+     * removed as a part of finishing the root activity of the task.
+     */
+    public void finishAndRemoveTask() {
+        finish(FINISH_TASK_WITH_ROOT_ACTIVITY);
+    }
+
+    /**
+     * Ask that the local app instance of this activity be released to free up its memory.
+     * This is asking for the activity to be destroyed, but does <b>not</b> finish the activity --
+     * a new instance of the activity will later be re-created if needed due to the user
+     * navigating back to it.
+     *
+     * @return Returns true if the activity was in a state that it has started the process
+     * of destroying its current instance; returns false if for any reason this could not
+     * be done: it is currently visible to the user, it is already being destroyed, it is
+     * being finished, it hasn't yet saved its state, etc.
+     */
+    public boolean releaseInstance() {
+        try {
+            return ActivityTaskManager.getService().releaseActivityInstance(mToken);
+        } catch (RemoteException e) {
+            // Empty
+        }
+        return false;
+    }
+
+    /**
+     * Called when an activity you launched exits, giving you the requestCode
+     * you started it with, the resultCode it returned, and any additional
+     * data from it.  The <var>resultCode</var> will be
+     * {@link #RESULT_CANCELED} if the activity explicitly returned that,
+     * didn't return any result, or crashed during its operation.
+     *
+     * <p>You will receive this call immediately before onResume() when your
+     * activity is re-starting.
+     *
+     * <p>This method is never invoked if your activity sets
+     * {@link android.R.styleable#AndroidManifestActivity_noHistory noHistory} to
+     * <code>true</code>.
+     *
+     * @param requestCode The integer request code originally supplied to
+     *                    startActivityForResult(), allowing you to identify who this
+     *                    result came from.
+     * @param resultCode The integer result code returned by the child activity
+     *                   through its setResult().
+     * @param data An Intent, which can return result data to the caller
+     *               (various data can be attached to Intent "extras").
+     *
+     * @see #startActivityForResult
+     * @see #createPendingResult
+     * @see #setResult(int)
+     */
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    }
+
+    /**
+     * Called when an activity you launched with an activity transition exposes this
+     * Activity through a returning activity transition, giving you the resultCode
+     * and any additional data from it. This method will only be called if the activity
+     * set a result code other than {@link #RESULT_CANCELED} and it supports activity
+     * transitions with {@link Window#FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * <p>The purpose of this function is to let the called Activity send a hint about
+     * its state so that this underlying Activity can prepare to be exposed. A call to
+     * this method does not guarantee that the called Activity has or will be exiting soon.
+     * It only indicates that it will expose this Activity's Window and it has
+     * some data to pass to prepare it.</p>
+     *
+     * @param resultCode The integer result code returned by the child activity
+     *                   through its setResult().
+     * @param data An Intent, which can return result data to the caller
+     *               (various data can be attached to Intent "extras").
+     */
+    public void onActivityReenter(int resultCode, Intent data) {
+    }
+
+    /**
+     * Create a new PendingIntent object which you can hand to others
+     * for them to use to send result data back to your
+     * {@link #onActivityResult} callback.  The created object will be either
+     * one-shot (becoming invalid after a result is sent back) or multiple
+     * (allowing any number of results to be sent through it).
+     *
+     * @param requestCode Private request code for the sender that will be
+     * associated with the result data when it is returned.  The sender can not
+     * modify this value, allowing you to identify incoming results.
+     * @param data Default data to supply in the result, which may be modified
+     * by the sender.
+     * @param flags May be {@link PendingIntent#FLAG_ONE_SHOT PendingIntent.FLAG_ONE_SHOT},
+     * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE},
+     * {@link PendingIntent#FLAG_CANCEL_CURRENT PendingIntent.FLAG_CANCEL_CURRENT},
+     * {@link PendingIntent#FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT},
+     * or any of the flags as supported by
+     * {@link Intent#fillIn Intent.fillIn()} to control which unspecified parts
+     * of the intent that can be supplied when the actual send happens.
+     *
+     * @return Returns an existing or new PendingIntent matching the given
+     * parameters.  May return null only if
+     * {@link PendingIntent#FLAG_NO_CREATE PendingIntent.FLAG_NO_CREATE} has been
+     * supplied.
+     *
+     * @see PendingIntent
+     */
+    public PendingIntent createPendingResult(int requestCode, @NonNull Intent data,
+            @PendingIntent.Flags int flags) {
+        String packageName = getPackageName();
+        try {
+            data.prepareToLeaveProcess(this);
+            IIntentSender target =
+                ActivityManager.getService().getIntentSender(
+                        ActivityManager.INTENT_SENDER_ACTIVITY_RESULT, packageName,
+                        mParent == null ? mToken : mParent.mToken,
+                        mEmbeddedID, requestCode, new Intent[] { data }, null, flags, null,
+                        getUserId());
+            return target != null ? new PendingIntent(target) : null;
+        } catch (RemoteException e) {
+            // Empty
+        }
+        return null;
+    }
+
+    /**
+     * Change the desired orientation of this activity.  If the activity
+     * is currently in the foreground or otherwise impacting the screen
+     * orientation, the screen will immediately be changed (possibly causing
+     * the activity to be restarted). Otherwise, this will be used the next
+     * time the activity is visible.
+     *
+     * @param requestedOrientation An orientation constant as used in
+     * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
+     */
+    public void setRequestedOrientation(@ActivityInfo.ScreenOrientation int requestedOrientation) {
+        if (mParent == null) {
+            try {
+                ActivityTaskManager.getService().setRequestedOrientation(
+                        mToken, requestedOrientation);
+            } catch (RemoteException e) {
+                // Empty
+            }
+        } else {
+            mParent.setRequestedOrientation(requestedOrientation);
+        }
+    }
+
+    /**
+     * Return the current requested orientation of the activity.  This will
+     * either be the orientation requested in its component's manifest, or
+     * the last requested orientation given to
+     * {@link #setRequestedOrientation(int)}.
+     *
+     * @return Returns an orientation constant as used in
+     * {@link ActivityInfo#screenOrientation ActivityInfo.screenOrientation}.
+     */
+    @ActivityInfo.ScreenOrientation
+    public int getRequestedOrientation() {
+        if (mParent == null) {
+            try {
+                return ActivityTaskManager.getService()
+                        .getRequestedOrientation(mToken);
+            } catch (RemoteException e) {
+                // Empty
+            }
+        } else {
+            return mParent.getRequestedOrientation();
+        }
+        return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+    }
+
+    /**
+     * Return the identifier of the task this activity is in.  This identifier
+     * will remain the same for the lifetime of the activity.
+     *
+     * @return Task identifier, an opaque integer.
+     */
+    public int getTaskId() {
+        try {
+            return ActivityTaskManager.getService().getTaskForActivity(mToken, false);
+        } catch (RemoteException e) {
+            return -1;
+        }
+    }
+
+    /**
+     * Return whether this activity is the root of a task.  The root is the
+     * first activity in a task.
+     *
+     * @return True if this is the root activity, else false.
+     */
+    @Override
+    public boolean isTaskRoot() {
+        try {
+            return ActivityTaskManager.getService().getTaskForActivity(mToken, true) >= 0;
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Move the task containing this activity to the back of the activity
+     * stack.  The activity's order within the task is unchanged.
+     *
+     * @param nonRoot If false then this only works if the activity is the root
+     *                of a task; if true it will work for any activity in
+     *                a task.
+     *
+     * @return If the task was moved (or it was already at the
+     *         back) true is returned, else false.
+     */
+    public boolean moveTaskToBack(boolean nonRoot) {
+        try {
+            return ActivityTaskManager.getService().moveActivityTaskToBack(mToken, nonRoot);
+        } catch (RemoteException e) {
+            // Empty
+        }
+        return false;
+    }
+
+    /**
+     * Returns class name for this activity with the package prefix removed.
+     * This is the default name used to read and write settings.
+     *
+     * @return The local class name.
+     */
+    @NonNull
+    public String getLocalClassName() {
+        final String pkg = getPackageName();
+        final String cls = mComponent.getClassName();
+        int packageLen = pkg.length();
+        if (!cls.startsWith(pkg) || cls.length() <= packageLen
+                || cls.charAt(packageLen) != '.') {
+            return cls;
+        }
+        return cls.substring(packageLen+1);
+    }
+
+    /**
+     * Returns the complete component name of this activity.
+     *
+     * @return Returns the complete component name for this activity
+     */
+    public ComponentName getComponentName() {
+        return mComponent;
+    }
+
+    /** @hide */
+    @Override
+    public final ComponentName autofillClientGetComponentName() {
+        return getComponentName();
+    }
+
+    /** @hide */
+    @Override
+    public final ComponentName contentCaptureClientGetComponentName() {
+        return getComponentName();
+    }
+
+    /**
+     * Retrieve a {@link SharedPreferences} object for accessing preferences
+     * that are private to this activity.  This simply calls the underlying
+     * {@link #getSharedPreferences(String, int)} method by passing in this activity's
+     * class name as the preferences name.
+     *
+     * @param mode Operating mode.  Use {@link #MODE_PRIVATE} for the default
+     *             operation.
+     *
+     * @return Returns the single SharedPreferences instance that can be used
+     *         to retrieve and modify the preference values.
+     */
+    public SharedPreferences getPreferences(@Context.PreferencesMode int mode) {
+        return getSharedPreferences(getLocalClassName(), mode);
+    }
+
+    private void ensureSearchManager() {
+        if (mSearchManager != null) {
+            return;
+        }
+
+        try {
+            mSearchManager = new SearchManager(this, null);
+        } catch (ServiceNotFoundException e) {
+            throw new IllegalStateException(e);
+        }
+    }
+
+    @Override
+    public Object getSystemService(@ServiceName @NonNull String name) {
+        if (getBaseContext() == null) {
+            throw new IllegalStateException(
+                    "System services not available to Activities before onCreate()");
+        }
+
+        if (WINDOW_SERVICE.equals(name)) {
+            return mWindowManager;
+        } else if (SEARCH_SERVICE.equals(name)) {
+            ensureSearchManager();
+            return mSearchManager;
+        }
+        return super.getSystemService(name);
+    }
+
+    /**
+     * Change the title associated with this activity.  If this is a
+     * top-level activity, the title for its window will change.  If it
+     * is an embedded activity, the parent can do whatever it wants
+     * with it.
+     */
+    public void setTitle(CharSequence title) {
+        mTitle = title;
+        onTitleChanged(title, mTitleColor);
+
+        if (mParent != null) {
+            mParent.onChildTitleChanged(this, title);
+        }
+    }
+
+    /**
+     * Change the title associated with this activity.  If this is a
+     * top-level activity, the title for its window will change.  If it
+     * is an embedded activity, the parent can do whatever it wants
+     * with it.
+     */
+    public void setTitle(int titleId) {
+        setTitle(getText(titleId));
+    }
+
+    /**
+     * Change the color of the title associated with this activity.
+     * <p>
+     * This method is deprecated starting in API Level 11 and replaced by action
+     * bar styles. For information on styling the Action Bar, read the <a
+     * href="{@docRoot} guide/topics/ui/actionbar.html">Action Bar</a> developer
+     * guide.
+     *
+     * @deprecated Use action bar styles instead.
+     */
+    @Deprecated
+    public void setTitleColor(int textColor) {
+        mTitleColor = textColor;
+        onTitleChanged(mTitle, textColor);
+    }
+
+    public final CharSequence getTitle() {
+        return mTitle;
+    }
+
+    public final int getTitleColor() {
+        return mTitleColor;
+    }
+
+    protected void onTitleChanged(CharSequence title, int color) {
+        if (mTitleReady) {
+            final Window win = getWindow();
+            if (win != null) {
+                win.setTitle(title);
+                if (color != 0) {
+                    win.setTitleColor(color);
+                }
+            }
+            if (mActionBar != null) {
+                mActionBar.setWindowTitle(title);
+            }
+        }
+    }
+
+    protected void onChildTitleChanged(Activity childActivity, CharSequence title) {
+    }
+
+    /**
+     * Sets information describing the task with this activity for presentation inside the Recents
+     * System UI. When {@link ActivityManager#getRecentTasks} is called, the activities of each task
+     * are traversed in order from the topmost activity to the bottommost. The traversal continues
+     * for each property until a suitable value is found. For each task the taskDescription will be
+     * returned in {@link android.app.ActivityManager.TaskDescription}.
+     *
+     * @see ActivityManager#getRecentTasks
+     * @see android.app.ActivityManager.TaskDescription
+     *
+     * @param taskDescription The TaskDescription properties that describe the task with this activity
+     */
+    public void setTaskDescription(ActivityManager.TaskDescription taskDescription) {
+        if (mTaskDescription != taskDescription) {
+            mTaskDescription.copyFromPreserveHiddenFields(taskDescription);
+            // Scale the icon down to something reasonable if it is provided
+            if (taskDescription.getIconFilename() == null && taskDescription.getIcon() != null) {
+                final int size = ActivityManager.getLauncherLargeIconSizeInner(this);
+                final Bitmap icon = Bitmap.createScaledBitmap(taskDescription.getIcon(), size, size,
+                        true);
+                mTaskDescription.setIcon(icon);
+            }
+        }
+        try {
+            ActivityTaskManager.getService().setTaskDescription(mToken, mTaskDescription);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Sets the visibility of the progress bar in the title.
+     * <p>
+     * In order for the progress bar to be shown, the feature must be requested
+     * via {@link #requestWindowFeature(int)}.
+     *
+     * @param visible Whether to show the progress bars in the title.
+     * @deprecated No longer supported starting in API 21.
+     */
+    @Deprecated
+    public final void setProgressBarVisibility(boolean visible) {
+        getWindow().setFeatureInt(Window.FEATURE_PROGRESS, visible ? Window.PROGRESS_VISIBILITY_ON :
+            Window.PROGRESS_VISIBILITY_OFF);
+    }
+
+    /**
+     * Sets the visibility of the indeterminate progress bar in the title.
+     * <p>
+     * In order for the progress bar to be shown, the feature must be requested
+     * via {@link #requestWindowFeature(int)}.
+     *
+     * @param visible Whether to show the progress bars in the title.
+     * @deprecated No longer supported starting in API 21.
+     */
+    @Deprecated
+    public final void setProgressBarIndeterminateVisibility(boolean visible) {
+        getWindow().setFeatureInt(Window.FEATURE_INDETERMINATE_PROGRESS,
+                visible ? Window.PROGRESS_VISIBILITY_ON : Window.PROGRESS_VISIBILITY_OFF);
+    }
+
+    /**
+     * Sets whether the horizontal progress bar in the title should be indeterminate (the circular
+     * is always indeterminate).
+     * <p>
+     * In order for the progress bar to be shown, the feature must be requested
+     * via {@link #requestWindowFeature(int)}.
+     *
+     * @param indeterminate Whether the horizontal progress bar should be indeterminate.
+     * @deprecated No longer supported starting in API 21.
+     */
+    @Deprecated
+    public final void setProgressBarIndeterminate(boolean indeterminate) {
+        getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+                indeterminate ? Window.PROGRESS_INDETERMINATE_ON
+                        : Window.PROGRESS_INDETERMINATE_OFF);
+    }
+
+    /**
+     * Sets the progress for the progress bars in the title.
+     * <p>
+     * In order for the progress bar to be shown, the feature must be requested
+     * via {@link #requestWindowFeature(int)}.
+     *
+     * @param progress The progress for the progress bar. Valid ranges are from
+     *            0 to 10000 (both inclusive). If 10000 is given, the progress
+     *            bar will be completely filled and will fade out.
+     * @deprecated No longer supported starting in API 21.
+     */
+    @Deprecated
+    public final void setProgress(int progress) {
+        getWindow().setFeatureInt(Window.FEATURE_PROGRESS, progress + Window.PROGRESS_START);
+    }
+
+    /**
+     * Sets the secondary progress for the progress bar in the title. This
+     * progress is drawn between the primary progress (set via
+     * {@link #setProgress(int)} and the background. It can be ideal for media
+     * scenarios such as showing the buffering progress while the default
+     * progress shows the play progress.
+     * <p>
+     * In order for the progress bar to be shown, the feature must be requested
+     * via {@link #requestWindowFeature(int)}.
+     *
+     * @param secondaryProgress The secondary progress for the progress bar. Valid ranges are from
+     *            0 to 10000 (both inclusive).
+     * @deprecated No longer supported starting in API 21.
+     */
+    @Deprecated
+    public final void setSecondaryProgress(int secondaryProgress) {
+        getWindow().setFeatureInt(Window.FEATURE_PROGRESS,
+                secondaryProgress + Window.PROGRESS_SECONDARY_START);
+    }
+
+    /**
+     * Suggests an audio stream whose volume should be changed by the hardware
+     * volume controls.
+     * <p>
+     * The suggested audio stream will be tied to the window of this Activity.
+     * Volume requests which are received while the Activity is in the
+     * foreground will affect this stream.
+     * <p>
+     * It is not guaranteed that the hardware volume controls will always change
+     * this stream's volume (for example, if a call is in progress, its stream's
+     * volume may be changed instead). To reset back to the default, use
+     * {@link AudioManager#USE_DEFAULT_STREAM_TYPE}.
+     *
+     * @param streamType The type of the audio stream whose volume should be
+     *            changed by the hardware volume controls.
+     */
+    public final void setVolumeControlStream(int streamType) {
+        getWindow().setVolumeControlStream(streamType);
+    }
+
+    /**
+     * Gets the suggested audio stream whose volume should be changed by the
+     * hardware volume controls.
+     *
+     * @return The suggested audio stream type whose volume should be changed by
+     *         the hardware volume controls.
+     * @see #setVolumeControlStream(int)
+     */
+    public final int getVolumeControlStream() {
+        return getWindow().getVolumeControlStream();
+    }
+
+    /**
+     * Sets a {@link MediaController} to send media keys and volume changes to.
+     * <p>
+     * The controller will be tied to the window of this Activity. Media key and
+     * volume events which are received while the Activity is in the foreground
+     * will be forwarded to the controller and used to invoke transport controls
+     * or adjust the volume. This may be used instead of or in addition to
+     * {@link #setVolumeControlStream} to affect a specific session instead of a
+     * specific stream.
+     * <p>
+     * It is not guaranteed that the hardware volume controls will always change
+     * this session's volume (for example, if a call is in progress, its
+     * stream's volume may be changed instead). To reset back to the default use
+     * null as the controller.
+     *
+     * @param controller The controller for the session which should receive
+     *            media keys and volume changes.
+     */
+    public final void setMediaController(MediaController controller) {
+        getWindow().setMediaController(controller);
+    }
+
+    /**
+     * Gets the controller which should be receiving media key and volume events
+     * while this activity is in the foreground.
+     *
+     * @return The controller which should receive events.
+     * @see #setMediaController(android.media.session.MediaController)
+     */
+    public final MediaController getMediaController() {
+        return getWindow().getMediaController();
+    }
+
+    /**
+     * Runs the specified action on the UI thread. If the current thread is the UI
+     * thread, then the action is executed immediately. If the current thread is
+     * not the UI thread, the action is posted to the event queue of the UI thread.
+     *
+     * @param action the action to run on the UI thread
+     */
+    public final void runOnUiThread(Runnable action) {
+        if (Thread.currentThread() != mUiThread) {
+            mHandler.post(action);
+        } else {
+            action.run();
+        }
+    }
+
+    /** @hide */
+    @Override
+    public final void autofillClientRunOnUiThread(Runnable action) {
+        runOnUiThread(action);
+    }
+
+    /**
+     * Standard implementation of
+     * {@link android.view.LayoutInflater.Factory#onCreateView} used when
+     * inflating with the LayoutInflater returned by {@link #getSystemService}.
+     * This implementation does nothing and is for
+     * pre-{@link android.os.Build.VERSION_CODES#HONEYCOMB} apps.  Newer apps
+     * should use {@link #onCreateView(View, String, Context, AttributeSet)}.
+     *
+     * @see android.view.LayoutInflater#createView
+     * @see android.view.Window#getLayoutInflater
+     */
+    @Nullable
+    public View onCreateView(@NonNull String name, @NonNull Context context,
+            @NonNull AttributeSet attrs) {
+        return null;
+    }
+
+    /**
+     * Standard implementation of
+     * {@link android.view.LayoutInflater.Factory2#onCreateView(View, String, Context, AttributeSet)}
+     * used when inflating with the LayoutInflater returned by {@link #getSystemService}.
+     * This implementation handles <fragment> tags to embed fragments inside
+     * of the activity.
+     *
+     * @see android.view.LayoutInflater#createView
+     * @see android.view.Window#getLayoutInflater
+     */
+    @Nullable
+    public View onCreateView(@Nullable View parent, @NonNull String name,
+            @NonNull Context context, @NonNull AttributeSet attrs) {
+        if (!"fragment".equals(name)) {
+            return onCreateView(name, context, attrs);
+        }
+
+        return mFragments.onCreateView(parent, name, context, attrs);
+    }
+
+    /**
+     * Print the Activity's state into the given stream.  This gets invoked if
+     * you run "adb shell dumpsys activity &lt;activity_component_name&gt;".
+     *
+     * @param prefix Desired prefix to prepend at each line of output.
+     * @param fd The raw file descriptor that the dump is being sent to.
+     * @param writer The PrintWriter to which you should dump your state.  This will be
+     * closed for you after you return.
+     * @param args additional arguments to the dump request.
+     */
+    public void dump(@NonNull String prefix, @Nullable FileDescriptor fd,
+            @NonNull PrintWriter writer, @Nullable String[] args) {
+        dumpInner(prefix, fd, writer, args);
+    }
+
+    void dumpInner(@NonNull String prefix, @Nullable FileDescriptor fd,
+            @NonNull PrintWriter writer, @Nullable String[] args) {
+        if (args != null && args.length > 0) {
+            // Handle special cases
+            switch (args[0]) {
+                case "--autofill":
+                    dumpAutofillManager(prefix, writer);
+                    return;
+                case "--contentcapture":
+                    dumpContentCaptureManager(prefix, writer);
+                    return;
+            }
+        }
+        writer.print(prefix); writer.print("Local Activity ");
+                writer.print(Integer.toHexString(System.identityHashCode(this)));
+                writer.println(" State:");
+        String innerPrefix = prefix + "  ";
+        writer.print(innerPrefix); writer.print("mResumed=");
+                writer.print(mResumed); writer.print(" mStopped=");
+                writer.print(mStopped); writer.print(" mFinished=");
+                writer.println(mFinished);
+        writer.print(innerPrefix); writer.print("mChangingConfigurations=");
+                writer.println(mChangingConfigurations);
+        writer.print(innerPrefix); writer.print("mCurrentConfig=");
+                writer.println(mCurrentConfig);
+
+        mFragments.dumpLoaders(innerPrefix, fd, writer, args);
+        mFragments.getFragmentManager().dump(innerPrefix, fd, writer, args);
+        if (mVoiceInteractor != null) {
+            mVoiceInteractor.dump(innerPrefix, fd, writer, args);
+        }
+
+        if (getWindow() != null &&
+                getWindow().peekDecorView() != null &&
+                getWindow().peekDecorView().getViewRootImpl() != null) {
+            getWindow().peekDecorView().getViewRootImpl().dump(prefix, fd, writer, args);
+        }
+
+        mHandler.getLooper().dump(new PrintWriterPrinter(writer), prefix);
+
+        dumpAutofillManager(prefix, writer);
+        dumpContentCaptureManager(prefix, writer);
+
+        ResourcesManager.getInstance().dump(prefix, writer);
+    }
+
+    void dumpAutofillManager(String prefix, PrintWriter writer) {
+        final AutofillManager afm = getAutofillManager();
+        if (afm != null) {
+            afm.dump(prefix, writer);
+            writer.print(prefix); writer.print("Autofill Compat Mode: ");
+            writer.println(isAutofillCompatibilityEnabled());
+        } else {
+            writer.print(prefix); writer.println("No AutofillManager");
+        }
+    }
+
+    void dumpContentCaptureManager(String prefix, PrintWriter writer) {
+        final ContentCaptureManager cm = getContentCaptureManager();
+        if (cm != null) {
+            cm.dump(prefix, writer);
+        } else {
+            writer.print(prefix); writer.println("No ContentCaptureManager");
+        }
+    }
+
+    /**
+     * Bit indicating that this activity is "immersive" and should not be
+     * interrupted by notifications if possible.
+     *
+     * This value is initially set by the manifest property
+     * <code>android:immersive</code> but may be changed at runtime by
+     * {@link #setImmersive}.
+     *
+     * @see #setImmersive(boolean)
+     * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE
+     */
+    public boolean isImmersive() {
+        try {
+            return ActivityTaskManager.getService().isImmersive(mToken);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Indication of whether this is the highest level activity in this task. Can be used to
+     * determine whether an activity launched by this activity was placed in the same task or
+     * another task.
+     *
+     * @return true if this is the topmost, non-finishing activity in its task.
+     */
+    final boolean isTopOfTask() {
+        if (mToken == null || mWindow == null) {
+            return false;
+        }
+        try {
+            return ActivityTaskManager.getService().isTopOfTask(getActivityToken());
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Convert a translucent themed Activity {@link android.R.attr#windowIsTranslucent} to a
+     * fullscreen opaque Activity.
+     * <p>
+     * Call this whenever the background of a translucent Activity has changed to become opaque.
+     * Doing so will allow the {@link android.view.Surface} of the Activity behind to be released.
+     * <p>
+     * This call has no effect on non-translucent activities or on activities with the
+     * {@link android.R.attr#windowIsFloating} attribute.
+     *
+     * @see #convertToTranslucent(android.app.Activity.TranslucentConversionListener,
+     * ActivityOptions)
+     * @see TranslucentConversionListener
+     *
+     * @hide
+     */
+    @SystemApi
+    public void convertFromTranslucent() {
+        try {
+            mTranslucentCallback = null;
+            if (ActivityTaskManager.getService().convertFromTranslucent(mToken)) {
+                WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, true);
+            }
+        } catch (RemoteException e) {
+            // pass
+        }
+    }
+
+    /**
+     * Convert a translucent themed Activity {@link android.R.attr#windowIsTranslucent} back from
+     * opaque to translucent following a call to {@link #convertFromTranslucent()}.
+     * <p>
+     * Calling this allows the Activity behind this one to be seen again. Once all such Activities
+     * have been redrawn {@link TranslucentConversionListener#onTranslucentConversionComplete} will
+     * be called indicating that it is safe to make this activity translucent again. Until
+     * {@link TranslucentConversionListener#onTranslucentConversionComplete} is called the image
+     * behind the frontmost Activity will be indeterminate.
+     * <p>
+     * This call has no effect on non-translucent activities or on activities with the
+     * {@link android.R.attr#windowIsFloating} attribute.
+     *
+     * @param callback the method to call when all visible Activities behind this one have been
+     * drawn and it is safe to make this Activity translucent again.
+     * @param options activity options delivered to the activity below this one. The options
+     * are retrieved using {@link #getActivityOptions}.
+     * @return <code>true</code> if Window was opaque and will become translucent or
+     * <code>false</code> if window was translucent and no change needed to be made.
+     *
+     * @see #convertFromTranslucent()
+     * @see TranslucentConversionListener
+     *
+     * @hide
+     */
+    @SystemApi
+    public boolean convertToTranslucent(TranslucentConversionListener callback,
+            ActivityOptions options) {
+        boolean drawComplete;
+        try {
+            mTranslucentCallback = callback;
+            mChangeCanvasToTranslucent = ActivityTaskManager.getService().convertToTranslucent(
+                    mToken, options == null ? null : options.toBundle());
+            WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, false);
+            drawComplete = true;
+        } catch (RemoteException e) {
+            // Make callback return as though it timed out.
+            mChangeCanvasToTranslucent = false;
+            drawComplete = false;
+        }
+        if (!mChangeCanvasToTranslucent && mTranslucentCallback != null) {
+            // Window is already translucent.
+            mTranslucentCallback.onTranslucentConversionComplete(drawComplete);
+        }
+        return mChangeCanvasToTranslucent;
+    }
+
+    /** @hide */
+    void onTranslucentConversionComplete(boolean drawComplete) {
+        if (mTranslucentCallback != null) {
+            mTranslucentCallback.onTranslucentConversionComplete(drawComplete);
+            mTranslucentCallback = null;
+        }
+        if (mChangeCanvasToTranslucent) {
+            WindowManagerGlobal.getInstance().changeCanvasOpacity(mToken, false);
+        }
+    }
+
+    /** @hide */
+    public void onNewActivityOptions(ActivityOptions options) {
+        mActivityTransitionState.setEnterActivityOptions(this, options);
+        if (!mStopped) {
+            mActivityTransitionState.enterReady(this);
+        }
+    }
+
+    /**
+     * Retrieve the ActivityOptions passed in from the launching activity or passed back
+     * from an activity launched by this activity in its call to {@link
+     * #convertToTranslucent(TranslucentConversionListener, ActivityOptions)}
+     *
+     * @return The ActivityOptions passed to {@link #convertToTranslucent}.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    ActivityOptions getActivityOptions() {
+        try {
+            return ActivityOptions.fromBundle(
+                    ActivityTaskManager.getService().getActivityOptions(mToken));
+        } catch (RemoteException e) {
+        }
+        return null;
+    }
+
+    /**
+     * Activities that want to remain visible behind a translucent activity above them must call
+     * this method anytime between the start of {@link #onResume()} and the return from
+     * {@link #onPause()}. If this call is successful then the activity will remain visible after
+     * {@link #onPause()} is called, and is allowed to continue playing media in the background.
+     *
+     * <p>The actions of this call are reset each time that this activity is brought to the
+     * front. That is, every time {@link #onResume()} is called the activity will be assumed
+     * to not have requested visible behind. Therefore, if you want this activity to continue to
+     * be visible in the background you must call this method again.
+     *
+     * <p>Only fullscreen opaque activities may make this call. I.e. this call is a nop
+     * for dialog and translucent activities.
+     *
+     * <p>Under all circumstances, the activity must stop playing and release resources prior to or
+     * within a call to {@link #onVisibleBehindCanceled()} or if this call returns false.
+     *
+     * <p>False will be returned any time this method is called between the return of onPause and
+     *      the next call to onResume.
+     *
+     * @deprecated This method's functionality is no longer supported as of
+     *             {@link android.os.Build.VERSION_CODES#O} and will be removed in a future release.
+     *
+     * @param visible true to notify the system that the activity wishes to be visible behind other
+     *                translucent activities, false to indicate otherwise. Resources must be
+     *                released when passing false to this method.
+     *
+     * @return the resulting visibiity state. If true the activity will remain visible beyond
+     *      {@link #onPause()} if the next activity is translucent or not fullscreen. If false
+     *      then the activity may not count on being visible behind other translucent activities,
+     *      and must stop any media playback and release resources.
+     *      Returning false may occur in lieu of a call to {@link #onVisibleBehindCanceled()} so
+     *      the return value must be checked.
+     *
+     * @see #onVisibleBehindCanceled()
+     */
+    @Deprecated
+    public boolean requestVisibleBehind(boolean visible) {
+        return false;
+    }
+
+    /**
+     * Called when a translucent activity over this activity is becoming opaque or another
+     * activity is being launched. Activities that override this method must call
+     * <code>super.onVisibleBehindCanceled()</code> or a SuperNotCalledException will be thrown.
+     *
+     * <p>When this method is called the activity has 500 msec to release any resources it may be
+     * using while visible in the background.
+     * If the activity has not returned from this method in 500 msec the system will destroy
+     * the activity and kill the process in order to recover the resources for another
+     * process. Otherwise {@link #onStop()} will be called following return.
+     *
+     * @see #requestVisibleBehind(boolean)
+     *
+     * @deprecated This method's functionality is no longer supported as of
+     * {@link android.os.Build.VERSION_CODES#O} and will be removed in a future release.
+     */
+    @Deprecated
+    @CallSuper
+    public void onVisibleBehindCanceled() {
+        mCalled = true;
+    }
+
+    /**
+     * Translucent activities may call this to determine if there is an activity below them that
+     * is currently set to be visible in the background.
+     *
+     * @deprecated This method's functionality is no longer supported as of
+     * {@link android.os.Build.VERSION_CODES#O} and will be removed in a future release.
+     *
+     * @return true if an activity below is set to visible according to the most recent call to
+     * {@link #requestVisibleBehind(boolean)}, false otherwise.
+     *
+     * @see #requestVisibleBehind(boolean)
+     * @see #onVisibleBehindCanceled()
+     * @see #onBackgroundVisibleBehindChanged(boolean)
+     * @hide
+     */
+    @Deprecated
+    @SystemApi
+    public boolean isBackgroundVisibleBehind() {
+        return false;
+    }
+
+    /**
+     * The topmost foreground activity will receive this call when the background visibility state
+     * of the activity below it changes.
+     *
+     * This call may be a consequence of {@link #requestVisibleBehind(boolean)} or might be
+     * due to a background activity finishing itself.
+     *
+     * @deprecated This method's functionality is no longer supported as of
+     * {@link android.os.Build.VERSION_CODES#O} and will be removed in a future release.
+     *
+     * @param visible true if a background activity is visible, false otherwise.
+     *
+     * @see #requestVisibleBehind(boolean)
+     * @see #onVisibleBehindCanceled()
+     * @hide
+     */
+    @Deprecated
+    @SystemApi
+    public void onBackgroundVisibleBehindChanged(boolean visible) {
+    }
+
+    /**
+     * Activities cannot draw during the period that their windows are animating in. In order
+     * to know when it is safe to begin drawing they can override this method which will be
+     * called when the entering animation has completed.
+     */
+    public void onEnterAnimationComplete() {
+    }
+
+    /**
+     * @hide
+     */
+    public void dispatchEnterAnimationComplete() {
+        mEnterAnimationComplete = true;
+        mInstrumentation.onEnterAnimationComplete();
+        onEnterAnimationComplete();
+        if (getWindow() != null && getWindow().getDecorView() != null) {
+            View decorView = getWindow().getDecorView();
+            decorView.getViewTreeObserver().dispatchOnEnterAnimationComplete();
+        }
+    }
+
+    /**
+     * Adjust the current immersive mode setting.
+     *
+     * Note that changing this value will have no effect on the activity's
+     * {@link android.content.pm.ActivityInfo} structure; that is, if
+     * <code>android:immersive</code> is set to <code>true</code>
+     * in the application's manifest entry for this activity, the {@link
+     * android.content.pm.ActivityInfo#flags ActivityInfo.flags} member will
+     * always have its {@link android.content.pm.ActivityInfo#FLAG_IMMERSIVE
+     * FLAG_IMMERSIVE} bit set.
+     *
+     * @see #isImmersive()
+     * @see android.content.pm.ActivityInfo#FLAG_IMMERSIVE
+     */
+    public void setImmersive(boolean i) {
+        try {
+            ActivityTaskManager.getService().setImmersive(mToken, i);
+        } catch (RemoteException e) {
+            // pass
+        }
+    }
+
+    /**
+     * Enable or disable virtual reality (VR) mode for this Activity.
+     *
+     * <p>VR mode is a hint to Android system to switch to a mode optimized for VR applications
+     * while this Activity has user focus.</p>
+     *
+     * <p>It is recommended that applications additionally declare
+     * {@link android.R.attr#enableVrMode} in their manifest to allow for smooth activity
+     * transitions when switching between VR activities.</p>
+     *
+     * <p>If the requested {@link android.service.vr.VrListenerService} component is not available,
+     * VR mode will not be started.  Developers can handle this case as follows:</p>
+     *
+     * <pre>
+     * String servicePackage = "com.whatever.app";
+     * String serviceClass = "com.whatever.app.MyVrListenerService";
+     *
+     * // Name of the component of the VrListenerService to start.
+     * ComponentName serviceComponent = new ComponentName(servicePackage, serviceClass);
+     *
+     * try {
+     *    setVrModeEnabled(true, myComponentName);
+     * } catch (PackageManager.NameNotFoundException e) {
+     *        List&lt;ApplicationInfo> installed = getPackageManager().getInstalledApplications(0);
+     *        boolean isInstalled = false;
+     *        for (ApplicationInfo app : installed) {
+     *            if (app.packageName.equals(servicePackage)) {
+     *                isInstalled = true;
+     *                break;
+     *            }
+     *        }
+     *        if (isInstalled) {
+     *            // Package is installed, but not enabled in Settings.  Let user enable it.
+     *            startActivity(new Intent(Settings.ACTION_VR_LISTENER_SETTINGS));
+     *        } else {
+     *            // Package is not installed.  Send an intent to download this.
+     *            sentIntentToLaunchAppStore(servicePackage);
+     *        }
+     * }
+     * </pre>
+     *
+     * @param enabled {@code true} to enable this mode.
+     * @param requestedComponent the name of the component to use as a
+     *        {@link android.service.vr.VrListenerService} while VR mode is enabled.
+     *
+     * @throws android.content.pm.PackageManager.NameNotFoundException if the given component
+     *    to run as a {@link android.service.vr.VrListenerService} is not installed, or has
+     *    not been enabled in user settings.
+     *
+     * @see android.content.pm.PackageManager#FEATURE_VR_MODE_HIGH_PERFORMANCE
+     * @see android.service.vr.VrListenerService
+     * @see android.provider.Settings#ACTION_VR_LISTENER_SETTINGS
+     * @see android.R.attr#enableVrMode
+     */
+    public void setVrModeEnabled(boolean enabled, @NonNull ComponentName requestedComponent)
+          throws PackageManager.NameNotFoundException {
+        try {
+            if (ActivityTaskManager.getService().setVrMode(mToken, enabled, requestedComponent)
+                    != 0) {
+                throw new PackageManager.NameNotFoundException(
+                        requestedComponent.flattenToString());
+            }
+        } catch (RemoteException e) {
+            // pass
+        }
+    }
+
+    /**
+     * Start an action mode of the default type {@link ActionMode#TYPE_PRIMARY}.
+     *
+     * @param callback Callback that will manage lifecycle events for this action mode
+     * @return The ActionMode that was started, or null if it was canceled
+     *
+     * @see ActionMode
+     */
+    @Nullable
+    public ActionMode startActionMode(ActionMode.Callback callback) {
+        return mWindow.getDecorView().startActionMode(callback);
+    }
+
+    /**
+     * Start an action mode of the given type.
+     *
+     * @param callback Callback that will manage lifecycle events for this action mode
+     * @param type One of {@link ActionMode#TYPE_PRIMARY} or {@link ActionMode#TYPE_FLOATING}.
+     * @return The ActionMode that was started, or null if it was canceled
+     *
+     * @see ActionMode
+     */
+    @Nullable
+    public ActionMode startActionMode(ActionMode.Callback callback, int type) {
+        return mWindow.getDecorView().startActionMode(callback, type);
+    }
+
+    /**
+     * Give the Activity a chance to control the UI for an action mode requested
+     * by the system.
+     *
+     * <p>Note: If you are looking for a notification callback that an action mode
+     * has been started for this activity, see {@link #onActionModeStarted(ActionMode)}.</p>
+     *
+     * @param callback The callback that should control the new action mode
+     * @return The new action mode, or <code>null</code> if the activity does not want to
+     *         provide special handling for this action mode. (It will be handled by the system.)
+     */
+    @Nullable
+    @Override
+    public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
+        // Only Primary ActionModes are represented in the ActionBar.
+        if (mActionModeTypeStarting == ActionMode.TYPE_PRIMARY) {
+            initWindowDecorActionBar();
+            if (mActionBar != null) {
+                return mActionBar.startActionMode(callback);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Nullable
+    @Override
+    public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
+        try {
+            mActionModeTypeStarting = type;
+            return onWindowStartingActionMode(callback);
+        } finally {
+            mActionModeTypeStarting = ActionMode.TYPE_PRIMARY;
+        }
+    }
+
+    /**
+     * Notifies the Activity that an action mode has been started.
+     * Activity subclasses overriding this method should call the superclass implementation.
+     *
+     * @param mode The new action mode.
+     */
+    @CallSuper
+    @Override
+    public void onActionModeStarted(ActionMode mode) {
+    }
+
+    /**
+     * Notifies the activity that an action mode has finished.
+     * Activity subclasses overriding this method should call the superclass implementation.
+     *
+     * @param mode The action mode that just finished.
+     */
+    @CallSuper
+    @Override
+    public void onActionModeFinished(ActionMode mode) {
+    }
+
+    /**
+     * Returns true if the app should recreate the task when navigating 'up' from this activity
+     * by using targetIntent.
+     *
+     * <p>If this method returns false the app can trivially call
+     * {@link #navigateUpTo(Intent)} using the same parameters to correctly perform
+     * up navigation. If this method returns false, the app should synthesize a new task stack
+     * by using {@link TaskStackBuilder} or another similar mechanism to perform up navigation.</p>
+     *
+     * @param targetIntent An intent representing the target destination for up navigation
+     * @return true if navigating up should recreate a new task stack, false if the same task
+     *         should be used for the destination
+     */
+    public boolean shouldUpRecreateTask(Intent targetIntent) {
+        try {
+            PackageManager pm = getPackageManager();
+            ComponentName cn = targetIntent.getComponent();
+            if (cn == null) {
+                cn = targetIntent.resolveActivity(pm);
+            }
+            ActivityInfo info = pm.getActivityInfo(cn, 0);
+            if (info.taskAffinity == null) {
+                return false;
+            }
+            return ActivityTaskManager.getService().shouldUpRecreateTask(mToken, info.taskAffinity);
+        } catch (RemoteException e) {
+            return false;
+        } catch (NameNotFoundException e) {
+            return false;
+        }
+    }
+
+    /**
+     * Navigate from this activity to the activity specified by upIntent, finishing this activity
+     * in the process. If the activity indicated by upIntent already exists in the task's history,
+     * this activity and all others before the indicated activity in the history stack will be
+     * finished.
+     *
+     * <p>If the indicated activity does not appear in the history stack, this will finish
+     * each activity in this task until the root activity of the task is reached, resulting in
+     * an "in-app home" behavior. This can be useful in apps with a complex navigation hierarchy
+     * when an activity may be reached by a path not passing through a canonical parent
+     * activity.</p>
+     *
+     * <p>This method should be used when performing up navigation from within the same task
+     * as the destination. If up navigation should cross tasks in some cases, see
+     * {@link #shouldUpRecreateTask(Intent)}.</p>
+     *
+     * @param upIntent An intent representing the target destination for up navigation
+     *
+     * @return true if up navigation successfully reached the activity indicated by upIntent and
+     *         upIntent was delivered to it. false if an instance of the indicated activity could
+     *         not be found and this activity was simply finished normally.
+     */
+    public boolean navigateUpTo(Intent upIntent) {
+        if (mParent == null) {
+            ComponentName destInfo = upIntent.getComponent();
+            if (destInfo == null) {
+                destInfo = upIntent.resolveActivity(getPackageManager());
+                if (destInfo == null) {
+                    return false;
+                }
+                upIntent = new Intent(upIntent);
+                upIntent.setComponent(destInfo);
+            }
+            int resultCode;
+            Intent resultData;
+            synchronized (this) {
+                resultCode = mResultCode;
+                resultData = mResultData;
+            }
+            if (resultData != null) {
+                resultData.prepareToLeaveProcess(this);
+            }
+            try {
+                upIntent.prepareToLeaveProcess(this);
+                return ActivityTaskManager.getService().navigateUpTo(mToken, upIntent,
+                        resultCode, resultData);
+            } catch (RemoteException e) {
+                return false;
+            }
+        } else {
+            return mParent.navigateUpToFromChild(this, upIntent);
+        }
+    }
+
+    /**
+     * This is called when a child activity of this one calls its
+     * {@link #navigateUpTo} method.  The default implementation simply calls
+     * navigateUpTo(upIntent) on this activity (the parent).
+     *
+     * @param child The activity making the call.
+     * @param upIntent An intent representing the target destination for up navigation
+     *
+     * @return true if up navigation successfully reached the activity indicated by upIntent and
+     *         upIntent was delivered to it. false if an instance of the indicated activity could
+     *         not be found and this activity was simply finished normally.
+     */
+    public boolean navigateUpToFromChild(Activity child, Intent upIntent) {
+        return navigateUpTo(upIntent);
+    }
+
+    /**
+     * Obtain an {@link Intent} that will launch an explicit target activity specified by
+     * this activity's logical parent. The logical parent is named in the application's manifest
+     * by the {@link android.R.attr#parentActivityName parentActivityName} attribute.
+     * Activity subclasses may override this method to modify the Intent returned by
+     * super.getParentActivityIntent() or to implement a different mechanism of retrieving
+     * the parent intent entirely.
+     *
+     * @return a new Intent targeting the defined parent of this activity or null if
+     *         there is no valid parent.
+     */
+    @Nullable
+    public Intent getParentActivityIntent() {
+        final String parentName = mActivityInfo.parentActivityName;
+        if (TextUtils.isEmpty(parentName)) {
+            return null;
+        }
+
+        // If the parent itself has no parent, generate a main activity intent.
+        final ComponentName target = new ComponentName(this, parentName);
+        try {
+            final ActivityInfo parentInfo = getPackageManager().getActivityInfo(target, 0);
+            final String parentActivity = parentInfo.parentActivityName;
+            final Intent parentIntent = parentActivity == null
+                    ? Intent.makeMainActivity(target)
+                    : new Intent().setComponent(target);
+            return parentIntent;
+        } catch (NameNotFoundException e) {
+            Log.e(TAG, "getParentActivityIntent: bad parentActivityName '" + parentName +
+                    "' in manifest");
+            return null;
+        }
+    }
+
+    /**
+     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+     * android.view.View, String)} was used to start an Activity, <var>callback</var>
+     * will be called to handle shared elements on the <i>launched</i> Activity. This requires
+     * {@link Window#FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @param callback Used to manipulate shared element transitions on the launched Activity.
+     */
+    public void setEnterSharedElementCallback(SharedElementCallback callback) {
+        if (callback == null) {
+            callback = SharedElementCallback.NULL_CALLBACK;
+        }
+        mEnterTransitionListener = callback;
+    }
+
+    /**
+     * When {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+     * android.view.View, String)} was used to start an Activity, <var>callback</var>
+     * will be called to handle shared elements on the <i>launching</i> Activity. Most
+     * calls will only come when returning from the started Activity.
+     * This requires {@link Window#FEATURE_ACTIVITY_TRANSITIONS}.
+     *
+     * @param callback Used to manipulate shared element transitions on the launching Activity.
+     */
+    public void setExitSharedElementCallback(SharedElementCallback callback) {
+        if (callback == null) {
+            callback = SharedElementCallback.NULL_CALLBACK;
+        }
+        mExitTransitionListener = callback;
+    }
+
+    /**
+     * Postpone the entering activity transition when Activity was started with
+     * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+     * android.util.Pair[])}.
+     * <p>This method gives the Activity the ability to delay starting the entering and
+     * shared element transitions until all data is loaded. Until then, the Activity won't
+     * draw into its window, leaving the window transparent. This may also cause the
+     * returning animation to be delayed until data is ready. This method should be
+     * called in {@link #onCreate(android.os.Bundle)} or in
+     * {@link #onActivityReenter(int, android.content.Intent)}.
+     * {@link #startPostponedEnterTransition()} must be called to allow the Activity to
+     * start the transitions. If the Activity did not use
+     * {@link android.app.ActivityOptions#makeSceneTransitionAnimation(Activity,
+     * android.util.Pair[])}, then this method does nothing.</p>
+     */
+    public void postponeEnterTransition() {
+        mActivityTransitionState.postponeEnterTransition();
+    }
+
+    /**
+     * Begin postponed transitions after {@link #postponeEnterTransition()} was called.
+     * If postponeEnterTransition() was called, you must call startPostponedEnterTransition()
+     * to have your Activity start drawing.
+     */
+    public void startPostponedEnterTransition() {
+        mActivityTransitionState.startPostponedEnterTransition();
+    }
+
+    /**
+     * Create {@link DragAndDropPermissions} object bound to this activity and controlling the
+     * access permissions for content URIs associated with the {@link DragEvent}.
+     * @param event Drag event
+     * @return The {@link DragAndDropPermissions} object used to control access to the content URIs.
+     * Null if no content URIs are associated with the event or if permissions could not be granted.
+     */
+    public DragAndDropPermissions requestDragAndDropPermissions(DragEvent event) {
+        DragAndDropPermissions dragAndDropPermissions = DragAndDropPermissions.obtain(event);
+        if (dragAndDropPermissions != null && dragAndDropPermissions.take(getActivityToken())) {
+            return dragAndDropPermissions;
+        }
+        return null;
+    }
+
+    // ------------------ Internal API ------------------
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    final void setParent(Activity parent) {
+        mParent = parent;
+    }
+
+    @UnsupportedAppUsage
+    final void attach(Context context, ActivityThread aThread,
+            Instrumentation instr, IBinder token, int ident,
+            Application application, Intent intent, ActivityInfo info,
+            CharSequence title, Activity parent, String id,
+            NonConfigurationInstances lastNonConfigurationInstances,
+            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
+            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
+        attachBaseContext(context);
+
+        mFragments.attachHost(null /*parent*/);
+
+        mWindow = new PhoneWindow(this, window, activityConfigCallback);
+        mWindow.setWindowControllerCallback(this);
+        mWindow.setCallback(this);
+        mWindow.setOnWindowDismissedCallback(this);
+        mWindow.getLayoutInflater().setPrivateFactory(this);
+        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
+            mWindow.setSoftInputMode(info.softInputMode);
+        }
+        if (info.uiOptions != 0) {
+            mWindow.setUiOptions(info.uiOptions);
+        }
+        mUiThread = Thread.currentThread();
+
+        mMainThread = aThread;
+        mInstrumentation = instr;
+        mToken = token;
+        mAssistToken = assistToken;
+        mIdent = ident;
+        mApplication = application;
+        mIntent = intent;
+        mReferrer = referrer;
+        mComponent = intent.getComponent();
+        mActivityInfo = info;
+        mTitle = title;
+        mParent = parent;
+        mEmbeddedID = id;
+        mLastNonConfigurationInstances = lastNonConfigurationInstances;
+        if (voiceInteractor != null) {
+            if (lastNonConfigurationInstances != null) {
+                mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
+            } else {
+                mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
+                        Looper.myLooper());
+            }
+        }
+
+        mWindow.setWindowManager(
+                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
+                mToken, mComponent.flattenToString(),
+                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
+        if (mParent != null) {
+            mWindow.setContainer(mParent.getWindow());
+        }
+        mWindowManager = mWindow.getWindowManager();
+        mCurrentConfig = config;
+
+        mWindow.setColorMode(info.colorMode);
+
+        setAutofillOptions(application.getAutofillOptions());
+        setContentCaptureOptions(application.getContentCaptureOptions());
+    }
+
+    private void enableAutofillCompatibilityIfNeeded() {
+        if (isAutofillCompatibilityEnabled()) {
+            final AutofillManager afm = getSystemService(AutofillManager.class);
+            if (afm != null) {
+                afm.enableCompatibilityMode();
+            }
+        }
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public final IBinder getActivityToken() {
+        return mParent != null ? mParent.getActivityToken() : mToken;
+    }
+
+    /** @hide */
+    public final IBinder getAssistToken() {
+        return mParent != null ? mParent.getAssistToken() : mAssistToken;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public final ActivityThread getActivityThread() {
+        return mMainThread;
+    }
+
+    final void performCreate(Bundle icicle) {
+        performCreate(icicle, null);
+    }
+
+    @UnsupportedAppUsage
+    final void performCreate(Bundle icicle, PersistableBundle persistentState) {
+        dispatchActivityPreCreated(icicle);
+        mCanEnterPictureInPicture = true;
+        restoreHasCurrentPermissionRequest(icicle);
+        if (persistentState != null) {
+            onCreate(icicle, persistentState);
+        } else {
+            onCreate(icicle);
+        }
+        writeEventLog(LOG_AM_ON_CREATE_CALLED, "performCreate");
+        mActivityTransitionState.readState(icicle);
+
+        mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
+                com.android.internal.R.styleable.Window_windowNoDisplay, false);
+        mFragments.dispatchActivityCreated();
+        mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+        dispatchActivityPostCreated(icicle);
+    }
+
+    final void performNewIntent(@NonNull Intent intent) {
+        mCanEnterPictureInPicture = true;
+        onNewIntent(intent);
+    }
+
+    final void performStart(String reason) {
+        dispatchActivityPreStarted();
+        mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
+        mFragments.noteStateNotSaved();
+        mCalled = false;
+        mFragments.execPendingActions();
+        mInstrumentation.callActivityOnStart(this);
+        writeEventLog(LOG_AM_ON_START_CALLED, reason);
+
+        if (!mCalled) {
+            throw new SuperNotCalledException(
+                "Activity " + mComponent.toShortString() +
+                " did not call through to super.onStart()");
+        }
+        mFragments.dispatchStart();
+        mFragments.reportLoaderStart();
+
+        boolean isAppDebuggable =
+                (mApplication.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+
+        // This property is set for all non-user builds except final release
+        boolean isDlwarningEnabled = SystemProperties.getInt("ro.bionic.ld.warning", 0) == 1;
+
+        if (isAppDebuggable || isDlwarningEnabled) {
+            String dlwarning = getDlWarning();
+            if (dlwarning != null) {
+                String appName = getApplicationInfo().loadLabel(getPackageManager())
+                        .toString();
+                String warning = "Detected problems with app native libraries\n" +
+                                 "(please consult log for detail):\n" + dlwarning;
+                if (isAppDebuggable) {
+                      new AlertDialog.Builder(this).
+                          setTitle(appName).
+                          setMessage(warning).
+                          setPositiveButton(android.R.string.ok, null).
+                          setCancelable(false).
+                          show();
+                } else {
+                    Toast.makeText(this, appName + "\n" + warning, Toast.LENGTH_LONG).show();
+                }
+            }
+        }
+
+        GraphicsEnvironment.getInstance().showAngleInUseDialogBox(this);
+
+        mActivityTransitionState.enterReady(this);
+        dispatchActivityPostStarted();
+    }
+
+    /**
+     * Restart the activity.
+     * @param start Indicates whether the activity should also be started after restart.
+     *              The option to not start immediately is needed in case a transaction with
+     *              multiple lifecycle transitions is in progress.
+     */
+    final void performRestart(boolean start, String reason) {
+        mCanEnterPictureInPicture = true;
+        mFragments.noteStateNotSaved();
+
+        if (mToken != null && mParent == null) {
+            // No need to check mStopped, the roots will check if they were actually stopped.
+            WindowManagerGlobal.getInstance().setStoppedState(mToken, false /* stopped */);
+        }
+
+        if (mStopped) {
+            mStopped = false;
+
+            synchronized (mManagedCursors) {
+                final int N = mManagedCursors.size();
+                for (int i=0; i<N; i++) {
+                    ManagedCursor mc = mManagedCursors.get(i);
+                    if (mc.mReleased || mc.mUpdated) {
+                        if (!mc.mCursor.requery()) {
+                            if (getApplicationInfo().targetSdkVersion
+                                    >= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+                                throw new IllegalStateException(
+                                        "trying to requery an already closed cursor  "
+                                        + mc.mCursor);
+                            }
+                        }
+                        mc.mReleased = false;
+                        mc.mUpdated = false;
+                    }
+                }
+            }
+
+            mCalled = false;
+            mInstrumentation.callActivityOnRestart(this);
+            writeEventLog(LOG_AM_ON_RESTART_CALLED, reason);
+            if (!mCalled) {
+                throw new SuperNotCalledException(
+                    "Activity " + mComponent.toShortString() +
+                    " did not call through to super.onRestart()");
+            }
+            if (start) {
+                performStart(reason);
+            }
+        }
+    }
+
+    final void performResume(boolean followedByPause, String reason) {
+        dispatchActivityPreResumed();
+        performRestart(true /* start */, reason);
+
+        mFragments.execPendingActions();
+
+        mLastNonConfigurationInstances = null;
+
+        if (mAutoFillResetNeeded) {
+            // When Activity is destroyed in paused state, and relaunch activity, there will be
+            // extra onResume and onPause event,  ignore the first onResume and onPause.
+            // see ActivityThread.handleRelaunchActivity()
+            mAutoFillIgnoreFirstResumePause = followedByPause;
+            if (mAutoFillIgnoreFirstResumePause && DEBUG_LIFECYCLE) {
+                Slog.v(TAG, "autofill will ignore first pause when relaunching " + this);
+            }
+        }
+
+        mCalled = false;
+        // mResumed is set by the instrumentation
+        mInstrumentation.callActivityOnResume(this);
+        writeEventLog(LOG_AM_ON_RESUME_CALLED, reason);
+        if (!mCalled) {
+            throw new SuperNotCalledException(
+                "Activity " + mComponent.toShortString() +
+                " did not call through to super.onResume()");
+        }
+
+        // invisible activities must be finished before onResume() completes
+        if (!mVisibleFromClient && !mFinished) {
+            Log.w(TAG, "An activity without a UI must call finish() before onResume() completes");
+            if (getApplicationInfo().targetSdkVersion
+                    > android.os.Build.VERSION_CODES.LOLLIPOP_MR1) {
+                throw new IllegalStateException(
+                        "Activity " + mComponent.toShortString() +
+                        " did not call finish() prior to onResume() completing");
+            }
+        }
+
+        // Now really resume, and install the current status bar and menu.
+        mCalled = false;
+
+        mFragments.dispatchResume();
+        mFragments.execPendingActions();
+
+        onPostResume();
+        if (!mCalled) {
+            throw new SuperNotCalledException(
+                "Activity " + mComponent.toShortString() +
+                " did not call through to super.onPostResume()");
+        }
+        dispatchActivityPostResumed();
+    }
+
+    final void performPause() {
+        dispatchActivityPrePaused();
+        mDoReportFullyDrawn = false;
+        mFragments.dispatchPause();
+        mCalled = false;
+        onPause();
+        writeEventLog(LOG_AM_ON_PAUSE_CALLED, "performPause");
+        mResumed = false;
+        if (!mCalled && getApplicationInfo().targetSdkVersion
+                >= android.os.Build.VERSION_CODES.GINGERBREAD) {
+            throw new SuperNotCalledException(
+                    "Activity " + mComponent.toShortString() +
+                    " did not call through to super.onPause()");
+        }
+        dispatchActivityPostPaused();
+    }
+
+    final void performUserLeaving() {
+        onUserInteraction();
+        onUserLeaveHint();
+    }
+
+    final void performStop(boolean preserveWindow, String reason) {
+        mDoReportFullyDrawn = false;
+        mFragments.doLoaderStop(mChangingConfigurations /*retain*/);
+
+        // Disallow entering picture-in-picture after the activity has been stopped
+        mCanEnterPictureInPicture = false;
+
+        if (!mStopped) {
+            dispatchActivityPreStopped();
+            if (mWindow != null) {
+                mWindow.closeAllPanels();
+            }
+
+            // If we're preserving the window, don't setStoppedState to true, since we
+            // need the window started immediately again. Stopping the window will
+            // destroys hardware resources and causes flicker.
+            if (!preserveWindow && mToken != null && mParent == null) {
+                WindowManagerGlobal.getInstance().setStoppedState(mToken, true);
+            }
+
+            mFragments.dispatchStop();
+
+            mCalled = false;
+            mInstrumentation.callActivityOnStop(this);
+            writeEventLog(LOG_AM_ON_STOP_CALLED, reason);
+            if (!mCalled) {
+                throw new SuperNotCalledException(
+                    "Activity " + mComponent.toShortString() +
+                    " did not call through to super.onStop()");
+            }
+
+            synchronized (mManagedCursors) {
+                final int N = mManagedCursors.size();
+                for (int i=0; i<N; i++) {
+                    ManagedCursor mc = mManagedCursors.get(i);
+                    if (!mc.mReleased) {
+                        mc.mCursor.deactivate();
+                        mc.mReleased = true;
+                    }
+                }
+            }
+
+            mStopped = true;
+            dispatchActivityPostStopped();
+        }
+        mResumed = false;
+    }
+
+    final void performDestroy() {
+        dispatchActivityPreDestroyed();
+        mDestroyed = true;
+        mWindow.destroy();
+        mFragments.dispatchDestroy();
+        onDestroy();
+        writeEventLog(LOG_AM_ON_DESTROY_CALLED, "performDestroy");
+        mFragments.doLoaderDestroy();
+        if (mVoiceInteractor != null) {
+            mVoiceInteractor.detachActivity();
+        }
+        dispatchActivityPostDestroyed();
+    }
+
+    final void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode,
+            Configuration newConfig) {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG,
+                "dispatchMultiWindowModeChanged " + this + ": " + isInMultiWindowMode
+                        + " " + newConfig);
+        mFragments.dispatchMultiWindowModeChanged(isInMultiWindowMode, newConfig);
+        if (mWindow != null) {
+            mWindow.onMultiWindowModeChanged();
+        }
+        onMultiWindowModeChanged(isInMultiWindowMode, newConfig);
+    }
+
+    final void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode,
+            Configuration newConfig) {
+        if (DEBUG_LIFECYCLE) Slog.v(TAG,
+                "dispatchPictureInPictureModeChanged " + this + ": " + isInPictureInPictureMode
+                        + " " + newConfig);
+        mFragments.dispatchPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
+        if (mWindow != null) {
+            mWindow.onPictureInPictureModeChanged(isInPictureInPictureMode);
+        }
+        onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig);
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public final boolean isResumed() {
+        return mResumed;
+    }
+
+    private void storeHasCurrentPermissionRequest(Bundle bundle) {
+        if (bundle != null && mHasCurrentPermissionsRequest) {
+            bundle.putBoolean(HAS_CURENT_PERMISSIONS_REQUEST_KEY, true);
+        }
+    }
+
+    private void restoreHasCurrentPermissionRequest(Bundle bundle) {
+        if (bundle != null) {
+            mHasCurrentPermissionsRequest = bundle.getBoolean(
+                    HAS_CURENT_PERMISSIONS_REQUEST_KEY, false);
+        }
+    }
+
+    @UnsupportedAppUsage
+    void dispatchActivityResult(String who, int requestCode, int resultCode, Intent data,
+            String reason) {
+        if (false) Log.v(
+            TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+            + ", resCode=" + resultCode + ", data=" + data);
+        mFragments.noteStateNotSaved();
+        if (who == null) {
+            onActivityResult(requestCode, resultCode, data);
+        } else if (who.startsWith(REQUEST_PERMISSIONS_WHO_PREFIX)) {
+            who = who.substring(REQUEST_PERMISSIONS_WHO_PREFIX.length());
+            if (TextUtils.isEmpty(who)) {
+                dispatchRequestPermissionsResult(requestCode, data);
+            } else {
+                Fragment frag = mFragments.findFragmentByWho(who);
+                if (frag != null) {
+                    dispatchRequestPermissionsResultToFragment(requestCode, data, frag);
+                }
+            }
+        } else if (who.startsWith("@android:view:")) {
+            ArrayList<ViewRootImpl> views = WindowManagerGlobal.getInstance().getRootViews(
+                    getActivityToken());
+            for (ViewRootImpl viewRoot : views) {
+                if (viewRoot.getView() != null
+                        && viewRoot.getView().dispatchActivityResult(
+                                who, requestCode, resultCode, data)) {
+                    return;
+                }
+            }
+        } else if (who.startsWith(AUTO_FILL_AUTH_WHO_PREFIX)) {
+            Intent resultData = (resultCode == Activity.RESULT_OK) ? data : null;
+            getAutofillManager().onAuthenticationResult(requestCode, resultData, getCurrentFocus());
+        } else {
+            Fragment frag = mFragments.findFragmentByWho(who);
+            if (frag != null) {
+                frag.onActivityResult(requestCode, resultCode, data);
+            }
+        }
+        writeEventLog(LOG_AM_ON_ACTIVITY_RESULT_CALLED, reason);
+    }
+
+    /**
+     * Request to put this activity in a mode where the user is locked to a restricted set of
+     * applications.
+     *
+     * <p>If {@link DevicePolicyManager#isLockTaskPermitted(String)} returns {@code true}
+     * for this component, the current task will be launched directly into LockTask mode. Only apps
+     * whitelisted by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])} can
+     * be launched while LockTask mode is active. The user will not be able to leave this mode
+     * until this activity calls {@link #stopLockTask()}. Calling this method while the device is
+     * already in LockTask mode has no effect.
+     *
+     * <p>Otherwise, the current task will be launched into screen pinning mode. In this case, the
+     * system will prompt the user with a dialog requesting permission to use this mode.
+     * The user can exit at any time through instructions shown on the request dialog. Calling
+     * {@link #stopLockTask()} will also terminate this mode.
+     *
+     * <p><strong>Note:</strong> this method can only be called when the activity is foreground.
+     * That is, between {@link #onResume()} and {@link #onPause()}.
+     *
+     * @see #stopLockTask()
+     * @see android.R.attr#lockTaskMode
+     */
+    public void startLockTask() {
+        try {
+            ActivityTaskManager.getService().startLockTaskModeByToken(mToken);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Stop the current task from being locked.
+     *
+     * <p>Called to end the LockTask or screen pinning mode started by {@link #startLockTask()}.
+     * This can only be called by activities that have called {@link #startLockTask()} previously.
+     *
+     * <p><strong>Note:</strong> If the device is in LockTask mode that is not initially started
+     * by this activity, then calling this method will not terminate the LockTask mode, but only
+     * finish its own task. The device will remain in LockTask mode, until the activity which
+     * started the LockTask mode calls this method, or until its whitelist authorization is revoked
+     * by {@link DevicePolicyManager#setLockTaskPackages(ComponentName, String[])}.
+     *
+     * @see #startLockTask()
+     * @see android.R.attr#lockTaskMode
+     * @see ActivityManager#getLockTaskModeState()
+     */
+    public void stopLockTask() {
+        try {
+            ActivityTaskManager.getService().stopLockTaskModeByToken(mToken);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Shows the user the system defined message for telling the user how to exit
+     * lock task mode. The task containing this activity must be in lock task mode at the time
+     * of this call for the message to be displayed.
+     */
+    public void showLockTaskEscapeMessage() {
+        try {
+            ActivityTaskManager.getService().showLockTaskEscapeMessage(mToken);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Check whether the caption on freeform windows is displayed directly on the content.
+     *
+     * @return True if caption is displayed on content, false if it pushes the content down.
+     *
+     * @see #setOverlayWithDecorCaptionEnabled(boolean)
+     * @hide
+     */
+    public boolean isOverlayWithDecorCaptionEnabled() {
+        return mWindow.isOverlayWithDecorCaptionEnabled();
+    }
+
+    /**
+     * Set whether the caption should displayed directly on the content rather than push it down.
+     *
+     * This affects only freeform windows since they display the caption and only the main
+     * window of the activity. The caption is used to drag the window around and also shows
+     * maximize and close action buttons.
+     * @hide
+     */
+    public void setOverlayWithDecorCaptionEnabled(boolean enabled) {
+        mWindow.setOverlayWithDecorCaptionEnabled(enabled);
+    }
+
+    /**
+     * Interface for informing a translucent {@link Activity} once all visible activities below it
+     * have completed drawing. This is necessary only after an {@link Activity} has been made
+     * opaque using {@link Activity#convertFromTranslucent()} and before it has been drawn
+     * translucent again following a call to {@link
+     * Activity#convertToTranslucent(android.app.Activity.TranslucentConversionListener,
+     * ActivityOptions)}
+     *
+     * @hide
+     */
+    @SystemApi
+    public interface TranslucentConversionListener {
+        /**
+         * Callback made following {@link Activity#convertToTranslucent} once all visible Activities
+         * below the top one have been redrawn. Following this callback it is safe to make the top
+         * Activity translucent because the underlying Activity has been drawn.
+         *
+         * @param drawComplete True if the background Activity has drawn itself. False if a timeout
+         * occurred waiting for the Activity to complete drawing.
+         *
+         * @see Activity#convertFromTranslucent()
+         * @see Activity#convertToTranslucent(TranslucentConversionListener, ActivityOptions)
+         */
+        public void onTranslucentConversionComplete(boolean drawComplete);
+    }
+
+    private void dispatchRequestPermissionsResult(int requestCode, Intent data) {
+        mHasCurrentPermissionsRequest = false;
+        // If the package installer crashed we may have not data - best effort.
+        String[] permissions = (data != null) ? data.getStringArrayExtra(
+                PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
+        final int[] grantResults = (data != null) ? data.getIntArrayExtra(
+                PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
+        onRequestPermissionsResult(requestCode, permissions, grantResults);
+    }
+
+    private void dispatchRequestPermissionsResultToFragment(int requestCode, Intent data,
+            Fragment fragment) {
+        // If the package installer crashed we may have not data - best effort.
+        String[] permissions = (data != null) ? data.getStringArrayExtra(
+                PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
+        final int[] grantResults = (data != null) ? data.getIntArrayExtra(
+                PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
+        fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
+    }
+
+    /** @hide */
+    @Override
+    public final void autofillClientAuthenticate(int authenticationId, IntentSender intent,
+            Intent fillInIntent) {
+        try {
+            startIntentSenderForResultInner(intent, AUTO_FILL_AUTH_WHO_PREFIX,
+                    authenticationId, fillInIntent, 0, 0, null);
+        } catch (IntentSender.SendIntentException e) {
+            Log.e(TAG, "authenticate() failed for intent:" + intent, e);
+        }
+    }
+
+    /** @hide */
+    @Override
+    public final void autofillClientResetableStateAvailable() {
+        mAutoFillResetNeeded = true;
+    }
+
+    /** @hide */
+    @Override
+    public final boolean autofillClientRequestShowFillUi(@NonNull View anchor, int width,
+            int height, @Nullable Rect anchorBounds, IAutofillWindowPresenter presenter) {
+        final boolean wasShowing;
+
+        if (mAutofillPopupWindow == null) {
+            wasShowing = false;
+            mAutofillPopupWindow = new AutofillPopupWindow(presenter);
+        } else {
+            wasShowing = mAutofillPopupWindow.isShowing();
+        }
+        mAutofillPopupWindow.update(anchor, 0, 0, width, height, anchorBounds);
+
+        return !wasShowing && mAutofillPopupWindow.isShowing();
+    }
+
+    /** @hide */
+    @Override
+    public final void autofillClientDispatchUnhandledKey(@NonNull View anchor,
+            @NonNull KeyEvent keyEvent) {
+        ViewRootImpl rootImpl = anchor.getViewRootImpl();
+        if (rootImpl != null) {
+            // dont care if anchorView is current focus, for example a custom view may only receive
+            // touchEvent, not focusable but can still trigger autofill window. The Key handling
+            // might be inside parent of the custom view.
+            rootImpl.dispatchKeyFromAutofill(keyEvent);
+        }
+    }
+
+    /** @hide */
+    @Override
+    public final boolean autofillClientRequestHideFillUi() {
+        if (mAutofillPopupWindow == null) {
+            return false;
+        }
+        mAutofillPopupWindow.dismiss();
+        mAutofillPopupWindow = null;
+        return true;
+    }
+
+    /** @hide */
+    @Override
+    public final boolean autofillClientIsFillUiShowing() {
+        return mAutofillPopupWindow != null && mAutofillPopupWindow.isShowing();
+    }
+
+    /** @hide */
+    @Override
+    @NonNull
+    public final View[] autofillClientFindViewsByAutofillIdTraversal(
+            @NonNull AutofillId[] autofillId) {
+        final View[] views = new View[autofillId.length];
+        final ArrayList<ViewRootImpl> roots =
+                WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
+
+        for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
+            final View rootView = roots.get(rootNum).getView();
+
+            if (rootView != null) {
+                final int viewCount = autofillId.length;
+                for (int viewNum = 0; viewNum < viewCount; viewNum++) {
+                    if (views[viewNum] == null) {
+                        views[viewNum] = rootView.findViewByAutofillIdTraversal(
+                                autofillId[viewNum].getViewId());
+                    }
+                }
+            }
+        }
+
+        return views;
+    }
+
+    /** @hide */
+    @Override
+    @Nullable
+    public final View autofillClientFindViewByAutofillIdTraversal(AutofillId autofillId) {
+        final ArrayList<ViewRootImpl> roots =
+                WindowManagerGlobal.getInstance().getRootViews(getActivityToken());
+        for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
+            final View rootView = roots.get(rootNum).getView();
+
+            if (rootView != null) {
+                final View view = rootView.findViewByAutofillIdTraversal(autofillId.getViewId());
+                if (view != null) {
+                    return view;
+                }
+            }
+        }
+
+        return null;
+    }
+
+    /** @hide */
+    @Override
+    public final @NonNull boolean[] autofillClientGetViewVisibility(
+            @NonNull AutofillId[] autofillIds) {
+        final int autofillIdCount = autofillIds.length;
+        final boolean[] visible = new boolean[autofillIdCount];
+        for (int i = 0; i < autofillIdCount; i++) {
+            final AutofillId autofillId = autofillIds[i];
+            final View view = autofillClientFindViewByAutofillIdTraversal(autofillId);
+            if (view != null) {
+                if (!autofillId.isVirtualInt()) {
+                    visible[i] = view.isVisibleToUser();
+                } else {
+                    visible[i] = view.isVisibleToUserForAutofill(autofillId.getVirtualChildIntId());
+                }
+            }
+        }
+        if (android.view.autofill.Helper.sVerbose) {
+            Log.v(TAG, "autofillClientGetViewVisibility(): " + Arrays.toString(visible));
+        }
+        return visible;
+    }
+
+    /** @hide */
+    public final @Nullable View autofillClientFindViewByAccessibilityIdTraversal(int viewId,
+            int windowId) {
+        final ArrayList<ViewRootImpl> roots = WindowManagerGlobal.getInstance()
+                .getRootViews(getActivityToken());
+        for (int rootNum = 0; rootNum < roots.size(); rootNum++) {
+            final View rootView = roots.get(rootNum).getView();
+            if (rootView != null && rootView.getAccessibilityWindowId() == windowId) {
+                final View view = rootView.findViewByAccessibilityIdTraversal(viewId);
+                if (view != null) {
+                    return view;
+                }
+            }
+        }
+        return null;
+    }
+
+    /** @hide */
+    @Override
+    public final @Nullable IBinder autofillClientGetActivityToken() {
+        return getActivityToken();
+    }
+
+    /** @hide */
+    @Override
+    public final boolean autofillClientIsVisibleForAutofill() {
+        return !mStopped;
+    }
+
+    /** @hide */
+    @Override
+    public final boolean autofillClientIsCompatibilityModeEnabled() {
+        return isAutofillCompatibilityEnabled();
+    }
+
+    /** @hide */
+    @Override
+    public final boolean isDisablingEnterExitEventForAutofill() {
+        return mAutoFillIgnoreFirstResumePause || !mResumed;
+    }
+
+    /**
+     * If set to true, this indicates to the system that it should never take a
+     * screenshot of the activity to be used as a representation while it is not in a started state.
+     * <p>
+     * Note that the system may use the window background of the theme instead to represent
+     * the window when it is not running.
+     * <p>
+     * Also note that in comparison to {@link android.view.WindowManager.LayoutParams#FLAG_SECURE},
+     * this only affects the behavior when the activity's screenshot would be used as a
+     * representation when the activity is not in a started state, i.e. in Overview. The system may
+     * still take screenshots of the activity in other contexts; for example, when the user takes a
+     * screenshot of the entire screen, or when the active
+     * {@link android.service.voice.VoiceInteractionService} requests a screenshot via
+     * {@link android.service.voice.VoiceInteractionSession#SHOW_WITH_SCREENSHOT}.
+     *
+     * @param disable {@code true} to disable preview screenshots; {@code false} otherwise.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void setDisablePreviewScreenshots(boolean disable) {
+        try {
+            ActivityTaskManager.getService().setDisablePreviewScreenshots(mToken, disable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Specifies whether an {@link Activity} should be shown on top of the lock screen whenever
+     * the lockscreen is up and the activity is resumed. Normally an activity will be transitioned
+     * to the stopped state if it is started while the lockscreen is up, but with this flag set the
+     * activity will remain in the resumed state visible on-top of the lock screen. This value can
+     * be set as a manifest attribute using {@link android.R.attr#showWhenLocked}.
+     *
+     * @param showWhenLocked {@code true} to show the {@link Activity} on top of the lock screen;
+     *                                   {@code false} otherwise.
+     * @see #setTurnScreenOn(boolean)
+     * @see android.R.attr#turnScreenOn
+     * @see android.R.attr#showWhenLocked
+     */
+    public void setShowWhenLocked(boolean showWhenLocked) {
+        try {
+            ActivityTaskManager.getService().setShowWhenLocked(mToken, showWhenLocked);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Specifies whether this {@link Activity} should be shown on top of the lock screen whenever
+     * the lockscreen is up and this activity has another activity behind it with the showWhenLock
+     * attribute set. That is, this activity is only visible on the lock screen if there is another
+     * activity with the showWhenLock attribute visible at the same time on the lock screen. A use
+     * case for this is permission dialogs, that should only be visible on the lock screen if their
+     * requesting activity is also visible. This value can be set as a manifest attribute using
+     * android.R.attr#inheritShowWhenLocked.
+     *
+     * @param inheritShowWhenLocked {@code true} to show the {@link Activity} on top of the lock
+     *                              screen when this activity has another activity behind it with
+     *                              the showWhenLock attribute set; {@code false} otherwise.
+     * @see #setShowWhenLocked(boolean)
+     * @see android.R.attr#inheritShowWhenLocked
+     */
+    public void setInheritShowWhenLocked(boolean inheritShowWhenLocked) {
+        try {
+            ActivityTaskManager.getService().setInheritShowWhenLocked(
+                    mToken, inheritShowWhenLocked);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Specifies whether the screen should be turned on when the {@link Activity} is resumed.
+     * Normally an activity will be transitioned to the stopped state if it is started while the
+     * screen if off, but with this flag set the activity will cause the screen to turn on if the
+     * activity will be visible and resumed due to the screen coming on. The screen will not be
+     * turned on if the activity won't be visible after the screen is turned on. This flag is
+     * normally used in conjunction with the {@link android.R.attr#showWhenLocked} flag to make sure
+     * the activity is visible after the screen is turned on when the lockscreen is up. In addition,
+     * if this flag is set and the activity calls {@link
+     * KeyguardManager#requestDismissKeyguard(Activity, KeyguardManager.KeyguardDismissCallback)}
+     * the screen will turn on.
+     *
+     * @param turnScreenOn {@code true} to turn on the screen; {@code false} otherwise.
+     *
+     * @see #setShowWhenLocked(boolean)
+     * @see android.R.attr#turnScreenOn
+     * @see android.R.attr#showWhenLocked
+     */
+    public void setTurnScreenOn(boolean turnScreenOn) {
+        try {
+            ActivityTaskManager.getService().setTurnScreenOn(mToken, turnScreenOn);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers remote animations per transition type for this activity.
+     *
+     * @param definition The remote animation definition that defines which transition whould run
+     *                   which remote animation.
+     * @hide
+     */
+    @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+    @UnsupportedAppUsage
+    public void registerRemoteAnimations(RemoteAnimationDefinition definition) {
+        try {
+            ActivityTaskManager.getService().registerRemoteAnimations(mToken, definition);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Log a lifecycle event for current user id and component class. */
+    private void writeEventLog(int event, String reason) {
+        EventLog.writeEvent(event, UserHandle.myUserId(), getComponentName().getClassName(),
+                reason);
+    }
+
+    class HostCallbacks extends FragmentHostCallback<Activity> {
+        public HostCallbacks() {
+            super(Activity.this /*activity*/);
+        }
+
+        @Override
+        public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+            Activity.this.dump(prefix, fd, writer, args);
+        }
+
+        @Override
+        public boolean onShouldSaveFragmentState(Fragment fragment) {
+            return !isFinishing();
+        }
+
+        @Override
+        public LayoutInflater onGetLayoutInflater() {
+            final LayoutInflater result = Activity.this.getLayoutInflater();
+            if (onUseFragmentManagerInflaterFactory()) {
+                return result.cloneInContext(Activity.this);
+            }
+            return result;
+        }
+
+        @Override
+        public boolean onUseFragmentManagerInflaterFactory() {
+            // Newer platform versions use the child fragment manager's LayoutInflaterFactory.
+            return getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP;
+        }
+
+        @Override
+        public Activity onGetHost() {
+            return Activity.this;
+        }
+
+        @Override
+        public void onInvalidateOptionsMenu() {
+            Activity.this.invalidateOptionsMenu();
+        }
+
+        @Override
+        public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode,
+                Bundle options) {
+            Activity.this.startActivityFromFragment(fragment, intent, requestCode, options);
+        }
+
+        @Override
+        public void onStartActivityAsUserFromFragment(
+                Fragment fragment, Intent intent, int requestCode, Bundle options,
+                UserHandle user) {
+            Activity.this.startActivityAsUserFromFragment(
+                    fragment, intent, requestCode, options, user);
+        }
+
+        @Override
+        public void onStartIntentSenderFromFragment(Fragment fragment, IntentSender intent,
+                int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
+                int extraFlags, Bundle options) throws IntentSender.SendIntentException {
+            if (mParent == null) {
+                startIntentSenderForResultInner(intent, fragment.mWho, requestCode, fillInIntent,
+                        flagsMask, flagsValues, options);
+            } else if (options != null) {
+                mParent.startIntentSenderFromChildFragment(fragment, intent, requestCode,
+                        fillInIntent, flagsMask, flagsValues, extraFlags, options);
+            }
+        }
+
+        @Override
+        public void onRequestPermissionsFromFragment(Fragment fragment, String[] permissions,
+                int requestCode) {
+            String who = REQUEST_PERMISSIONS_WHO_PREFIX + fragment.mWho;
+            Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
+            startActivityForResult(who, intent, requestCode, null);
+        }
+
+        @Override
+        public boolean onHasWindowAnimations() {
+            return getWindow() != null;
+        }
+
+        @Override
+        public int onGetWindowAnimations() {
+            final Window w = getWindow();
+            return (w == null) ? 0 : w.getAttributes().windowAnimations;
+        }
+
+        @Override
+        public void onAttachFragment(Fragment fragment) {
+            Activity.this.onAttachFragment(fragment);
+        }
+
+        @Nullable
+        @Override
+        public <T extends View> T onFindViewById(int id) {
+            return Activity.this.findViewById(id);
+        }
+
+        @Override
+        public boolean onHasView() {
+            final Window w = getWindow();
+            return (w != null && w.peekDecorView() != null);
+        }
+    }
+}
diff --git a/android/app/ActivityGroup.java b/android/app/ActivityGroup.java
new file mode 100644
index 0000000..d4aa01b
--- /dev/null
+++ b/android/app/ActivityGroup.java
@@ -0,0 +1,132 @@
+/*
+ * 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.app;
+
+import android.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.os.Bundle;
+
+import java.util.HashMap;
+
+/**
+ * A screen that contains and runs multiple embedded activities.
+ *
+ * @deprecated Use the new {@link Fragment} and {@link FragmentManager} APIs
+ * instead; these are also
+ * available on older platforms through the Android compatibility package.
+ */
+@Deprecated
+public class ActivityGroup extends Activity {
+    private static final String STATES_KEY = "android:states";
+    static final String PARENT_NON_CONFIG_INSTANCE_KEY = "android:parent_non_config_instance";
+
+    /**
+     * This field should be made private, so it is hidden from the SDK.
+     * {@hide}
+     */
+    @UnsupportedAppUsage
+    protected LocalActivityManager mLocalActivityManager;
+    
+    public ActivityGroup() {
+        this(true);
+    }
+    
+    public ActivityGroup(boolean singleActivityMode) {
+        mLocalActivityManager = new LocalActivityManager(this, singleActivityMode);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Bundle states = savedInstanceState != null
+                ? (Bundle) savedInstanceState.getBundle(STATES_KEY) : null;
+        mLocalActivityManager.dispatchCreate(states);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mLocalActivityManager.dispatchResume();
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        Bundle state = mLocalActivityManager.saveInstanceState();
+        if (state != null) {
+            outState.putBundle(STATES_KEY, state);
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+        mLocalActivityManager.dispatchPause(isFinishing());
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        mLocalActivityManager.dispatchStop();
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        mLocalActivityManager.dispatchDestroy(isFinishing());
+    }
+
+    /**
+     * Returns a HashMap mapping from child activity ids to the return values
+     * from calls to their onRetainNonConfigurationInstance methods.
+     *
+     * {@hide}
+     */
+    @Override
+    public HashMap<String,Object> onRetainNonConfigurationChildInstances() {
+        return mLocalActivityManager.dispatchRetainNonConfigurationInstance();
+    }
+
+    public Activity getCurrentActivity() {
+        return mLocalActivityManager.getCurrentActivity();
+    }
+
+    public final LocalActivityManager getLocalActivityManager() {
+        return mLocalActivityManager;
+    }
+
+    @Override
+    void dispatchActivityResult(String who, int requestCode, int resultCode,
+            Intent data, String reason) {
+        if (who != null) {
+            Activity act = mLocalActivityManager.getActivity(who);
+            /*
+            if (false) Log.v(
+                TAG, "Dispatching result: who=" + who + ", reqCode=" + requestCode
+                + ", resCode=" + resultCode + ", data=" + data
+                + ", rec=" + rec);
+            */
+            if (act != null) {
+                act.onActivityResult(requestCode, resultCode, data);
+                return;
+            }
+        }
+        super.dispatchActivityResult(who, requestCode, resultCode, data, reason);
+    }
+}
+
+
diff --git a/android/app/ActivityManager.java b/android/app/ActivityManager.java
new file mode 100644
index 0000000..17368b7
--- /dev/null
+++ b/android/app/ActivityManager.java
@@ -0,0 +1,4326 @@
+/*
+ * 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.app;
+
+import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
+
+import android.Manifest;
+import android.annotation.DrawableRes;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ConfigurationInfo;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.UserInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.ColorSpace;
+import android.graphics.GraphicBuffer;
+import android.graphics.Matrix;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.BatteryStats;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.WorkSource;
+import android.util.ArrayMap;
+import android.util.DisplayMetrics;
+import android.util.Singleton;
+import android.util.Size;
+
+import com.android.internal.app.LocalePicker;
+import com.android.internal.app.procstats.ProcessStats;
+import com.android.internal.os.RoSystemProperties;
+import com.android.internal.os.TransferPipe;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.MemInfoReader;
+import com.android.server.LocalServices;
+
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.FileDescriptor;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+
+/**
+ * <p>
+ * This class gives information about, and interacts
+ * with, activities, services, and the containing
+ * process.
+ * </p>
+ *
+ * <p>
+ * A number of the methods in this class are for
+ * debugging or informational purposes and they should
+ * not be used to affect any runtime behavior of
+ * your app. These methods are called out as such in
+ * the method level documentation.
+ * </p>
+ *
+ *<p>
+ * Most application developers should not have the need to
+ * use this class, most of whose methods are for specialized
+ * use cases. However, a few methods are more broadly applicable.
+ * For instance, {@link android.app.ActivityManager#isLowRamDevice() isLowRamDevice()}
+ * enables your app to detect whether it is running on a low-memory device,
+ * and behave accordingly.
+ * {@link android.app.ActivityManager#clearApplicationUserData() clearApplicationUserData()}
+ * is for apps with reset-data functionality.
+ * </p>
+ *
+ * <p>
+ * In some special use cases, where an app interacts with
+ * its Task stack, the app may use the
+ * {@link android.app.ActivityManager.AppTask} and
+ * {@link android.app.ActivityManager.RecentTaskInfo} inner
+ * classes. However, in general, the methods in this class should
+ * be used for testing and debugging purposes only.
+ * </p>
+ */
+@SystemService(Context.ACTIVITY_SERVICE)
+public class ActivityManager {
+    private static String TAG = "ActivityManager";
+
+    @UnsupportedAppUsage
+    private final Context mContext;
+
+    private static volatile boolean sSystemReady = false;
+
+
+    private static final int FIRST_START_FATAL_ERROR_CODE = -100;
+    private static final int LAST_START_FATAL_ERROR_CODE = -1;
+    private static final int FIRST_START_SUCCESS_CODE = 0;
+    private static final int LAST_START_SUCCESS_CODE = 99;
+    private static final int FIRST_START_NON_FATAL_ERROR_CODE = 100;
+    private static final int LAST_START_NON_FATAL_ERROR_CODE = 199;
+
+    /**
+     * Disable hidden API checks for the newly started instrumentation.
+     * @hide
+     */
+    public static final int INSTR_FLAG_DISABLE_HIDDEN_API_CHECKS = 1 << 0;
+    /**
+     * Mount full external storage for the newly started instrumentation.
+     * @hide
+     */
+    public static final int INSTR_FLAG_MOUNT_EXTERNAL_STORAGE_FULL = 1 << 1;
+
+    static final class UidObserver extends IUidObserver.Stub {
+        final OnUidImportanceListener mListener;
+        final Context mContext;
+
+        UidObserver(OnUidImportanceListener listener, Context clientContext) {
+            mListener = listener;
+            mContext = clientContext;
+        }
+
+        @Override
+        public void onUidStateChanged(int uid, int procState, long procStateSeq) {
+            mListener.onUidImportance(uid, RunningAppProcessInfo.procStateToImportanceForClient(
+                    procState, mContext));
+        }
+
+        @Override
+        public void onUidGone(int uid, boolean disabled) {
+            mListener.onUidImportance(uid, RunningAppProcessInfo.IMPORTANCE_GONE);
+        }
+
+        @Override
+        public void onUidActive(int uid) {
+        }
+
+        @Override
+        public void onUidIdle(int uid, boolean disabled) {
+        }
+
+        @Override public void onUidCachedChanged(int uid, boolean cached) {
+        }
+    }
+
+    final ArrayMap<OnUidImportanceListener, UidObserver> mImportanceListeners = new ArrayMap<>();
+
+    /**
+     * Defines acceptable types of bugreports.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "BUGREPORT_OPTION_" }, value = {
+            BUGREPORT_OPTION_FULL,
+            BUGREPORT_OPTION_INTERACTIVE,
+            BUGREPORT_OPTION_REMOTE,
+            BUGREPORT_OPTION_WEAR,
+            BUGREPORT_OPTION_TELEPHONY,
+            BUGREPORT_OPTION_WIFI
+    })
+    public @interface BugreportMode {}
+    /**
+     * Takes a bugreport without user interference (and hence causing less
+     * interference to the system), but includes all sections.
+     * @hide
+     */
+    public static final int BUGREPORT_OPTION_FULL = 0;
+    /**
+     * Allows user to monitor progress and enter additional data; might not include all
+     * sections.
+     * @hide
+     */
+    public static final int BUGREPORT_OPTION_INTERACTIVE = 1;
+    /**
+     * Takes a bugreport requested remotely by administrator of the Device Owner app,
+     * not the device's user.
+     * @hide
+     */
+    public static final int BUGREPORT_OPTION_REMOTE = 2;
+    /**
+     * Takes a bugreport on a wearable device.
+     * @hide
+     */
+    public static final int BUGREPORT_OPTION_WEAR = 3;
+
+    /**
+     * Takes a lightweight version of bugreport that only includes a few, urgent sections
+     * used to report telephony bugs.
+     * @hide
+     */
+    public static final int BUGREPORT_OPTION_TELEPHONY = 4;
+
+    /**
+     * Takes a lightweight bugreport that only includes a few sections related to Wifi.
+     * @hide
+     */
+    public static final int BUGREPORT_OPTION_WIFI = 5;
+
+    /**
+     * <a href="{@docRoot}guide/topics/manifest/meta-data-element.html">{@code
+     * <meta-data>}</a> name for a 'home' Activity that declares a package that is to be
+     * uninstalled in lieu of the declaring one.  The package named here must be
+     * signed with the same certificate as the one declaring the {@code <meta-data>}.
+     */
+    public static final String META_HOME_ALTERNATE = "android.app.home.alternate";
+
+    // NOTE: Before adding a new start result, please reference the defined ranges to ensure the
+    // result is properly categorized.
+
+    /**
+     * Result for IActivityManager.startVoiceActivity: active session is currently hidden.
+     * @hide
+     */
+    public static final int START_VOICE_HIDDEN_SESSION = FIRST_START_FATAL_ERROR_CODE;
+
+    /**
+     * Result for IActivityManager.startVoiceActivity: active session does not match
+     * the requesting token.
+     * @hide
+     */
+    public static final int START_VOICE_NOT_ACTIVE_SESSION = FIRST_START_FATAL_ERROR_CODE + 1;
+
+    /**
+     * Result for IActivityManager.startActivity: trying to start a background user
+     * activity that shouldn't be displayed for all users.
+     * @hide
+     */
+    public static final int START_NOT_CURRENT_USER_ACTIVITY = FIRST_START_FATAL_ERROR_CODE + 2;
+
+    /**
+     * Result for IActivityManager.startActivity: trying to start an activity under voice
+     * control when that activity does not support the VOICE category.
+     * @hide
+     */
+    public static final int START_NOT_VOICE_COMPATIBLE = FIRST_START_FATAL_ERROR_CODE + 3;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * start had to be canceled.
+     * @hide
+     */
+    public static final int START_CANCELED = FIRST_START_FATAL_ERROR_CODE + 4;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * thing being started is not an activity.
+     * @hide
+     */
+    public static final int START_NOT_ACTIVITY = FIRST_START_FATAL_ERROR_CODE + 5;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * caller does not have permission to start the activity.
+     * @hide
+     */
+    public static final int START_PERMISSION_DENIED = FIRST_START_FATAL_ERROR_CODE + 6;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * caller has requested both to forward a result and to receive
+     * a result.
+     * @hide
+     */
+    public static final int START_FORWARD_AND_REQUEST_CONFLICT = FIRST_START_FATAL_ERROR_CODE + 7;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * requested class is not found.
+     * @hide
+     */
+    public static final int START_CLASS_NOT_FOUND = FIRST_START_FATAL_ERROR_CODE + 8;
+
+    /**
+     * Result for IActivityManager.startActivity: an error where the
+     * given Intent could not be resolved to an activity.
+     * @hide
+     */
+    public static final int START_INTENT_NOT_RESOLVED = FIRST_START_FATAL_ERROR_CODE + 9;
+
+    /**
+     * Result for IActivityManager.startAssistantActivity: active session is currently hidden.
+     * @hide
+     */
+    public static final int START_ASSISTANT_HIDDEN_SESSION = FIRST_START_FATAL_ERROR_CODE + 10;
+
+    /**
+     * Result for IActivityManager.startAssistantActivity: active session does not match
+     * the requesting token.
+     * @hide
+     */
+    public static final int START_ASSISTANT_NOT_ACTIVE_SESSION = FIRST_START_FATAL_ERROR_CODE + 11;
+
+    /**
+     * Result for IActivityManaqer.startActivity: the activity was started
+     * successfully as normal.
+     * @hide
+     */
+    public static final int START_SUCCESS = FIRST_START_SUCCESS_CODE;
+
+    /**
+     * Result for IActivityManaqer.startActivity: the caller asked that the Intent not
+     * be executed if it is the recipient, and that is indeed the case.
+     * @hide
+     */
+    public static final int START_RETURN_INTENT_TO_CALLER = FIRST_START_SUCCESS_CODE + 1;
+
+    /**
+     * Result for IActivityManaqer.startActivity: activity wasn't really started, but
+     * a task was simply brought to the foreground.
+     * @hide
+     */
+    public static final int START_TASK_TO_FRONT = FIRST_START_SUCCESS_CODE + 2;
+
+    /**
+     * Result for IActivityManaqer.startActivity: activity wasn't really started, but
+     * the given Intent was given to the existing top activity.
+     * @hide
+     */
+    public static final int START_DELIVERED_TO_TOP = FIRST_START_SUCCESS_CODE + 3;
+
+    /**
+     * Result for IActivityManaqer.startActivity: request was canceled because
+     * app switches are temporarily canceled to ensure the user's last request
+     * (such as pressing home) is performed.
+     * @hide
+     */
+    public static final int START_SWITCHES_CANCELED = FIRST_START_NON_FATAL_ERROR_CODE;
+
+    /**
+     * Result for IActivityManaqer.startActivity: a new activity was attempted to be started
+     * while in Lock Task Mode.
+     * @hide
+     */
+    public static final int START_RETURN_LOCK_TASK_MODE_VIOLATION =
+            FIRST_START_NON_FATAL_ERROR_CODE + 1;
+
+    /**
+     * Result for IActivityManaqer.startActivity: a new activity start was aborted. Never returned
+     * externally.
+     * @hide
+     */
+    public static final int START_ABORTED = FIRST_START_NON_FATAL_ERROR_CODE + 2;
+
+    /**
+     * Flag for IActivityManaqer.startActivity: do special start mode where
+     * a new activity is launched only if it is needed.
+     * @hide
+     */
+    public static final int START_FLAG_ONLY_IF_NEEDED = 1<<0;
+
+    /**
+     * Flag for IActivityManaqer.startActivity: launch the app for
+     * debugging.
+     * @hide
+     */
+    public static final int START_FLAG_DEBUG = 1<<1;
+
+    /**
+     * Flag for IActivityManaqer.startActivity: launch the app for
+     * allocation tracking.
+     * @hide
+     */
+    public static final int START_FLAG_TRACK_ALLOCATION = 1<<2;
+
+    /**
+     * Flag for IActivityManaqer.startActivity: launch the app with
+     * native debugging support.
+     * @hide
+     */
+    public static final int START_FLAG_NATIVE_DEBUGGING = 1<<3;
+
+    /**
+     * Result for IActivityManaqer.broadcastIntent: success!
+     * @hide
+     */
+    public static final int BROADCAST_SUCCESS = 0;
+
+    /**
+     * Result for IActivityManaqer.broadcastIntent: attempt to broadcast
+     * a sticky intent without appropriate permission.
+     * @hide
+     */
+    public static final int BROADCAST_STICKY_CANT_HAVE_PERMISSION = -1;
+
+    /**
+     * Result for IActivityManager.broadcastIntent: trying to send a broadcast
+     * to a stopped user. Fail.
+     * @hide
+     */
+    public static final int BROADCAST_FAILED_USER_STOPPED = -2;
+
+    /**
+     * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+     * for a sendBroadcast operation.
+     * @hide
+     */
+    public static final int INTENT_SENDER_BROADCAST = 1;
+
+    /**
+     * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+     * for a startActivity operation.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int INTENT_SENDER_ACTIVITY = 2;
+
+    /**
+     * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+     * for an activity result operation.
+     * @hide
+     */
+    public static final int INTENT_SENDER_ACTIVITY_RESULT = 3;
+
+    /**
+     * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+     * for a startService operation.
+     * @hide
+     */
+    public static final int INTENT_SENDER_SERVICE = 4;
+
+    /**
+     * Type for IActivityManaqer.getIntentSender: this PendingIntent is
+     * for a startForegroundService operation.
+     * @hide
+     */
+    public static final int INTENT_SENDER_FOREGROUND_SERVICE = 5;
+
+    /** @hide User operation call: success! */
+    public static final int USER_OP_SUCCESS = 0;
+
+    /** @hide User operation call: given user id is not known. */
+    public static final int USER_OP_UNKNOWN_USER = -1;
+
+    /** @hide User operation call: given user id is the current user, can't be stopped. */
+    public static final int USER_OP_IS_CURRENT = -2;
+
+    /** @hide User operation call: system user can't be stopped. */
+    public static final int USER_OP_ERROR_IS_SYSTEM = -3;
+
+    /** @hide User operation call: one of related users cannot be stopped. */
+    public static final int USER_OP_ERROR_RELATED_USERS_CANNOT_STOP = -4;
+
+    /**
+     * @hide
+     * Process states, describing the kind of state a particular process is in.
+     * When updating these, make sure to also check all related references to the
+     * constant in code, and update these arrays:
+     *
+     * @see com.android.internal.app.procstats.ProcessState#PROCESS_STATE_TO_STATE
+     * @see com.android.server.am.ProcessList#sProcStateToProcMem
+     * @see com.android.server.am.ProcessList#sFirstAwakePssTimes
+     * @see com.android.server.am.ProcessList#sSameAwakePssTimes
+     * @see com.android.server.am.ProcessList#sTestFirstPssTimes
+     * @see com.android.server.am.ProcessList#sTestSamePssTimes
+     */
+
+    /** @hide Not a real process state. */
+    public static final int PROCESS_STATE_UNKNOWN = -1;
+
+    /** @hide Process is a persistent system process. */
+    public static final int PROCESS_STATE_PERSISTENT = 0;
+
+    /** @hide Process is a persistent system process and is doing UI. */
+    public static final int PROCESS_STATE_PERSISTENT_UI = 1;
+
+    /** @hide Process is hosting the current top activities.  Note that this covers
+     * all activities that are visible to the user. */
+    @UnsupportedAppUsage
+    public static final int PROCESS_STATE_TOP = 2;
+
+    /** @hide Process is hosting a foreground service with location type. */
+    public static final int PROCESS_STATE_FOREGROUND_SERVICE_LOCATION = 3;
+
+    /** @hide Process is bound to a TOP app. This is ranked below SERVICE_LOCATION so that
+     * it doesn't get the capability of location access while-in-use. */
+    public static final int PROCESS_STATE_BOUND_TOP = 4;
+
+    /** @hide Process is hosting a foreground service. */
+    @UnsupportedAppUsage
+    public static final int PROCESS_STATE_FOREGROUND_SERVICE = 5;
+
+    /** @hide Process is hosting a foreground service due to a system binding. */
+    @UnsupportedAppUsage
+    public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 6;
+
+    /** @hide Process is important to the user, and something they are aware of. */
+    public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 7;
+
+    /** @hide Process is important to the user, but not something they are aware of. */
+    @UnsupportedAppUsage
+    public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 8;
+
+    /** @hide Process is in the background transient so we will try to keep running. */
+    public static final int PROCESS_STATE_TRANSIENT_BACKGROUND = 9;
+
+    /** @hide Process is in the background running a backup/restore operation. */
+    public static final int PROCESS_STATE_BACKUP = 10;
+
+    /** @hide Process is in the background running a service.  Unlike oom_adj, this level
+     * is used for both the normal running in background state and the executing
+     * operations state. */
+    @UnsupportedAppUsage
+    public static final int PROCESS_STATE_SERVICE = 11;
+
+    /** @hide Process is in the background running a receiver.   Note that from the
+     * perspective of oom_adj, receivers run at a higher foreground level, but for our
+     * prioritization here that is not necessary and putting them below services means
+     * many fewer changes in some process states as they receive broadcasts. */
+    @UnsupportedAppUsage
+    public static final int PROCESS_STATE_RECEIVER = 12;
+
+    /** @hide Same as {@link #PROCESS_STATE_TOP} but while device is sleeping. */
+    public static final int PROCESS_STATE_TOP_SLEEPING = 13;
+
+    /** @hide Process is in the background, but it can't restore its state so we want
+     * to try to avoid killing it. */
+    public static final int PROCESS_STATE_HEAVY_WEIGHT = 14;
+
+    /** @hide Process is in the background but hosts the home activity. */
+    @UnsupportedAppUsage
+    public static final int PROCESS_STATE_HOME = 15;
+
+    /** @hide Process is in the background but hosts the last shown activity. */
+    public static final int PROCESS_STATE_LAST_ACTIVITY = 16;
+
+    /** @hide Process is being cached for later use and contains activities. */
+    @UnsupportedAppUsage
+    public static final int PROCESS_STATE_CACHED_ACTIVITY = 17;
+
+    /** @hide Process is being cached for later use and is a client of another cached
+     * process that contains activities. */
+    public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 18;
+
+    /** @hide Process is being cached for later use and has an activity that corresponds
+     * to an existing recent task. */
+    public static final int PROCESS_STATE_CACHED_RECENT = 19;
+
+    /** @hide Process is being cached for later use and is empty. */
+    public static final int PROCESS_STATE_CACHED_EMPTY = 20;
+
+    /** @hide Process does not exist. */
+    public static final int PROCESS_STATE_NONEXISTENT = 21;
+
+    // NOTE: If PROCESS_STATEs are added, then new fields must be added
+    // to frameworks/base/core/proto/android/app/enums.proto and the following method must
+    // be updated to correctly map between them.
+    // However, if the current ActivityManager values are merely modified, no update should be made
+    // to enums.proto, to which values can only be added but never modified. Note that the proto
+    // versions do NOT have the ordering restrictions of the ActivityManager process state.
+    /**
+     * Maps ActivityManager.PROCESS_STATE_ values to enums.proto ProcessStateEnum value.
+     *
+     * @param amInt a process state of the form ActivityManager.PROCESS_STATE_
+     * @return the value of the corresponding enums.proto ProcessStateEnum value.
+     * @hide
+     */
+    public static final int processStateAmToProto(int amInt) {
+        switch (amInt) {
+            case PROCESS_STATE_UNKNOWN:
+                return AppProtoEnums.PROCESS_STATE_UNKNOWN;
+            case PROCESS_STATE_PERSISTENT:
+                return AppProtoEnums.PROCESS_STATE_PERSISTENT;
+            case PROCESS_STATE_PERSISTENT_UI:
+                return AppProtoEnums.PROCESS_STATE_PERSISTENT_UI;
+            case PROCESS_STATE_TOP:
+                return AppProtoEnums.PROCESS_STATE_TOP;
+            case PROCESS_STATE_BOUND_TOP:
+                return AppProtoEnums.PROCESS_STATE_BOUND_TOP;
+            case PROCESS_STATE_FOREGROUND_SERVICE_LOCATION:
+            case PROCESS_STATE_FOREGROUND_SERVICE:
+                return AppProtoEnums.PROCESS_STATE_FOREGROUND_SERVICE;
+            case PROCESS_STATE_BOUND_FOREGROUND_SERVICE:
+                return AppProtoEnums.PROCESS_STATE_BOUND_FOREGROUND_SERVICE;
+            case PROCESS_STATE_IMPORTANT_FOREGROUND:
+                return AppProtoEnums.PROCESS_STATE_IMPORTANT_FOREGROUND;
+            case PROCESS_STATE_IMPORTANT_BACKGROUND:
+                return AppProtoEnums.PROCESS_STATE_IMPORTANT_BACKGROUND;
+            case PROCESS_STATE_TRANSIENT_BACKGROUND:
+                return AppProtoEnums.PROCESS_STATE_TRANSIENT_BACKGROUND;
+            case PROCESS_STATE_BACKUP:
+                return AppProtoEnums.PROCESS_STATE_BACKUP;
+            case PROCESS_STATE_SERVICE:
+                return AppProtoEnums.PROCESS_STATE_SERVICE;
+            case PROCESS_STATE_RECEIVER:
+                return AppProtoEnums.PROCESS_STATE_RECEIVER;
+            case PROCESS_STATE_TOP_SLEEPING:
+                return AppProtoEnums.PROCESS_STATE_TOP_SLEEPING;
+            case PROCESS_STATE_HEAVY_WEIGHT:
+                return AppProtoEnums.PROCESS_STATE_HEAVY_WEIGHT;
+            case PROCESS_STATE_HOME:
+                return AppProtoEnums.PROCESS_STATE_HOME;
+            case PROCESS_STATE_LAST_ACTIVITY:
+                return AppProtoEnums.PROCESS_STATE_LAST_ACTIVITY;
+            case PROCESS_STATE_CACHED_ACTIVITY:
+                return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY;
+            case PROCESS_STATE_CACHED_ACTIVITY_CLIENT:
+                return AppProtoEnums.PROCESS_STATE_CACHED_ACTIVITY_CLIENT;
+            case PROCESS_STATE_CACHED_RECENT:
+                return AppProtoEnums.PROCESS_STATE_CACHED_RECENT;
+            case PROCESS_STATE_CACHED_EMPTY:
+                return AppProtoEnums.PROCESS_STATE_CACHED_EMPTY;
+            case PROCESS_STATE_NONEXISTENT:
+                return AppProtoEnums.PROCESS_STATE_NONEXISTENT;
+            default:
+                // ActivityManager process state (amInt)
+                // could not be mapped to an AppProtoEnums ProcessState state.
+                return AppProtoEnums.PROCESS_STATE_UNKNOWN_TO_PROTO;
+        }
+    }
+
+    /** @hide The lowest process state number */
+    public static final int MIN_PROCESS_STATE = PROCESS_STATE_PERSISTENT;
+
+    /** @hide The highest process state number */
+    public static final int MAX_PROCESS_STATE = PROCESS_STATE_NONEXISTENT;
+
+    /** @hide Should this process state be considered a background state? */
+    public static final boolean isProcStateBackground(int procState) {
+        return procState >= PROCESS_STATE_TRANSIENT_BACKGROUND;
+    }
+
+    /** @hide Is this a foreground service type? */
+    public static boolean isForegroundService(int procState) {
+        return procState == PROCESS_STATE_FOREGROUND_SERVICE_LOCATION
+                || procState == PROCESS_STATE_FOREGROUND_SERVICE;
+    }
+
+    /** @hide requestType for assist context: only basic information. */
+    public static final int ASSIST_CONTEXT_BASIC = 0;
+
+    /** @hide requestType for assist context: generate full AssistStructure. */
+    public static final int ASSIST_CONTEXT_FULL = 1;
+
+    /** @hide requestType for assist context: generate full AssistStructure for autofill. */
+    public static final int ASSIST_CONTEXT_AUTOFILL = 2;
+
+    /** @hide Flag for registerUidObserver: report changes in process state. */
+    public static final int UID_OBSERVER_PROCSTATE = 1<<0;
+
+    /** @hide Flag for registerUidObserver: report uid gone. */
+    public static final int UID_OBSERVER_GONE = 1<<1;
+
+    /** @hide Flag for registerUidObserver: report uid has become idle. */
+    public static final int UID_OBSERVER_IDLE = 1<<2;
+
+    /** @hide Flag for registerUidObserver: report uid has become active. */
+    public static final int UID_OBSERVER_ACTIVE = 1<<3;
+
+    /** @hide Flag for registerUidObserver: report uid cached state has changed. */
+    public static final int UID_OBSERVER_CACHED = 1<<4;
+
+    /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: normal free-to-run operation. */
+    public static final int APP_START_MODE_NORMAL = 0;
+
+    /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: delay running until later. */
+    public static final int APP_START_MODE_DELAYED = 1;
+
+    /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: delay running until later, with
+     * rigid errors (throwing exception). */
+    public static final int APP_START_MODE_DELAYED_RIGID = 2;
+
+    /** @hide Mode for {@link IActivityManager#isAppStartModeDisabled}: disable/cancel pending
+     * launches; this is the mode for ephemeral apps. */
+    public static final int APP_START_MODE_DISABLED = 3;
+
+    /**
+     * Lock task mode is not active.
+     */
+    public static final int LOCK_TASK_MODE_NONE = 0;
+
+    /**
+     * Full lock task mode is active.
+     */
+    public static final int LOCK_TASK_MODE_LOCKED = 1;
+
+    /**
+     * App pinning mode is active.
+     */
+    public static final int LOCK_TASK_MODE_PINNED = 2;
+
+    Point mAppTaskThumbnailSize;
+
+    @UnsupportedAppUsage
+    /*package*/ ActivityManager(Context context, Handler handler) {
+        mContext = context;
+    }
+
+    /**
+     * Returns whether the launch was successful.
+     * @hide
+     */
+    public static final boolean isStartResultSuccessful(int result) {
+        return FIRST_START_SUCCESS_CODE <= result && result <= LAST_START_SUCCESS_CODE;
+    }
+
+    /**
+     * Returns whether the launch result was a fatal error.
+     * @hide
+     */
+    public static final boolean isStartResultFatalError(int result) {
+        return FIRST_START_FATAL_ERROR_CODE <= result && result <= LAST_START_FATAL_ERROR_CODE;
+    }
+
+    /**
+     * Screen compatibility mode: the application most always run in
+     * compatibility mode.
+     * @hide
+     */
+    public static final int COMPAT_MODE_ALWAYS = -1;
+
+    /**
+     * Screen compatibility mode: the application can never run in
+     * compatibility mode.
+     * @hide
+     */
+    public static final int COMPAT_MODE_NEVER = -2;
+
+    /**
+     * Screen compatibility mode: unknown.
+     * @hide
+     */
+    public static final int COMPAT_MODE_UNKNOWN = -3;
+
+    /**
+     * Screen compatibility mode: the application currently has compatibility
+     * mode disabled.
+     * @hide
+     */
+    public static final int COMPAT_MODE_DISABLED = 0;
+
+    /**
+     * Screen compatibility mode: the application currently has compatibility
+     * mode enabled.
+     * @hide
+     */
+    public static final int COMPAT_MODE_ENABLED = 1;
+
+    /**
+     * Screen compatibility mode: request to toggle the application's
+     * compatibility mode.
+     * @hide
+     */
+    public static final int COMPAT_MODE_TOGGLE = 2;
+
+    private static final boolean DEVELOPMENT_FORCE_LOW_RAM =
+            SystemProperties.getBoolean("debug.force_low_ram", false);
+
+    /** @hide */
+    public int getFrontActivityScreenCompatMode() {
+        try {
+            return getTaskService().getFrontActivityScreenCompatMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void setFrontActivityScreenCompatMode(int mode) {
+        try {
+            getTaskService().setFrontActivityScreenCompatMode(mode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public int getPackageScreenCompatMode(String packageName) {
+        try {
+            return getTaskService().getPackageScreenCompatMode(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void setPackageScreenCompatMode(String packageName, int mode) {
+        try {
+            getTaskService().setPackageScreenCompatMode(packageName, mode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public boolean getPackageAskScreenCompat(String packageName) {
+        try {
+            return getTaskService().getPackageAskScreenCompat(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void setPackageAskScreenCompat(String packageName, boolean ask) {
+        try {
+            getTaskService().setPackageAskScreenCompat(packageName, ask);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the approximate per-application memory class of the current
+     * device.  This gives you an idea of how hard a memory limit you should
+     * impose on your application to let the overall system work best.  The
+     * returned value is in megabytes; the baseline Android memory class is
+     * 16 (which happens to be the Java heap limit of those devices); some
+     * devices with more memory may return 24 or even higher numbers.
+     */
+    public int getMemoryClass() {
+        return staticGetMemoryClass();
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    static public int staticGetMemoryClass() {
+        // Really brain dead right now -- just take this from the configured
+        // vm heap size, and assume it is in megabytes and thus ends with "m".
+        String vmHeapSize = SystemProperties.get("dalvik.vm.heapgrowthlimit", "");
+        if (vmHeapSize != null && !"".equals(vmHeapSize)) {
+            return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1));
+        }
+        return staticGetLargeMemoryClass();
+    }
+
+    /**
+     * Return the approximate per-application memory class of the current
+     * device when an application is running with a large heap.  This is the
+     * space available for memory-intensive applications; most applications
+     * should not need this amount of memory, and should instead stay with the
+     * {@link #getMemoryClass()} limit.  The returned value is in megabytes.
+     * This may be the same size as {@link #getMemoryClass()} on memory
+     * constrained devices, or it may be significantly larger on devices with
+     * a large amount of available RAM.
+     *
+     * <p>This is the size of the application's Dalvik heap if it has
+     * specified <code>android:largeHeap="true"</code> in its manifest.
+     */
+    public int getLargeMemoryClass() {
+        return staticGetLargeMemoryClass();
+    }
+
+    /** @hide */
+    static public int staticGetLargeMemoryClass() {
+        // Really brain dead right now -- just take this from the configured
+        // vm heap size, and assume it is in megabytes and thus ends with "m".
+        String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m");
+        return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length() - 1));
+    }
+
+    /**
+     * Returns true if this is a low-RAM device.  Exactly whether a device is low-RAM
+     * is ultimately up to the device configuration, but currently it generally means
+     * something with 1GB or less of RAM.  This is mostly intended to be used by apps
+     * to determine whether they should turn off certain features that require more RAM.
+     */
+    public boolean isLowRamDevice() {
+        return isLowRamDeviceStatic();
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static boolean isLowRamDeviceStatic() {
+        return RoSystemProperties.CONFIG_LOW_RAM ||
+                (Build.IS_DEBUGGABLE && DEVELOPMENT_FORCE_LOW_RAM);
+    }
+
+    /**
+     * Returns true if this is a small battery device. Exactly whether a device is considered to be
+     * small battery is ultimately up to the device configuration, but currently it generally means
+     * something in the class of a device with 1000 mAh or less. This is mostly intended to be used
+     * to determine whether certain features should be altered to account for a drastically smaller
+     * battery.
+     * @hide
+     */
+    public static boolean isSmallBatteryDevice() {
+        return RoSystemProperties.CONFIG_SMALL_BATTERY;
+    }
+
+    /**
+     * Used by persistent processes to determine if they are running on a
+     * higher-end device so should be okay using hardware drawing acceleration
+     * (which tends to consume a lot more RAM).
+     * @hide
+     */
+    @UnsupportedAppUsage
+    static public boolean isHighEndGfx() {
+        return !isLowRamDeviceStatic()
+                && !RoSystemProperties.CONFIG_AVOID_GFX_ACCEL
+                && !Resources.getSystem()
+                        .getBoolean(com.android.internal.R.bool.config_avoidGfxAccel);
+    }
+
+    /**
+     * Return the total number of bytes of RAM this device has.
+     * @hide
+     */
+    @TestApi
+    public long getTotalRam() {
+        MemInfoReader memreader = new MemInfoReader();
+        memreader.readMemInfo();
+        return memreader.getTotalSize();
+    }
+
+    /**
+     * TODO(b/80414790): Remove once no longer on hiddenapi-light-greylist.txt
+     * @hide
+     * @deprecated Use {@link ActivityTaskManager#getMaxRecentTasksStatic()}
+     */
+    @Deprecated
+    @UnsupportedAppUsage
+    static public int getMaxRecentTasksStatic() {
+        return ActivityTaskManager.getMaxRecentTasksStatic();
+    }
+
+    /** @removed */
+    @Deprecated
+    public static int getMaxNumPictureInPictureActions() {
+        return 3;
+    }
+
+    /**
+     * Information you can set and retrieve about the current activity within the recent task list.
+     */
+    public static class TaskDescription implements Parcelable {
+        /** @hide */
+        public static final String ATTR_TASKDESCRIPTION_PREFIX = "task_description_";
+        private static final String ATTR_TASKDESCRIPTIONLABEL =
+                ATTR_TASKDESCRIPTION_PREFIX + "label";
+        private static final String ATTR_TASKDESCRIPTIONCOLOR_PRIMARY =
+                ATTR_TASKDESCRIPTION_PREFIX + "color";
+        private static final String ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND =
+                ATTR_TASKDESCRIPTION_PREFIX + "colorBackground";
+        private static final String ATTR_TASKDESCRIPTIONICON_FILENAME =
+                ATTR_TASKDESCRIPTION_PREFIX + "icon_filename";
+        private static final String ATTR_TASKDESCRIPTIONICON_RESOURCE =
+                ATTR_TASKDESCRIPTION_PREFIX + "icon_resource";
+
+        private String mLabel;
+        private Bitmap mIcon;
+        private int mIconRes;
+        private String mIconFilename;
+        private int mColorPrimary;
+        private int mColorBackground;
+        private int mStatusBarColor;
+        private int mNavigationBarColor;
+        private boolean mEnsureStatusBarContrastWhenTransparent;
+        private boolean mEnsureNavigationBarContrastWhenTransparent;
+
+        /**
+         * Creates the TaskDescription to the specified values.
+         *
+         * @param label A label and description of the current state of this task.
+         * @param icon An icon that represents the current state of this task.
+         * @param colorPrimary A color to override the theme's primary color.  This color must be
+         *                     opaque.
+         * @deprecated use TaskDescription constructor with icon resource instead
+         */
+        @Deprecated
+        public TaskDescription(String label, Bitmap icon, int colorPrimary) {
+            this(label, icon, 0, null, colorPrimary, 0, 0, 0, false, false);
+            if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
+                throw new RuntimeException("A TaskDescription's primary color should be opaque");
+            }
+        }
+
+        /**
+         * Creates the TaskDescription to the specified values.
+         *
+         * @param label A label and description of the current state of this task.
+         * @param iconRes A drawable resource of an icon that represents the current state of this
+         *                activity.
+         * @param colorPrimary A color to override the theme's primary color.  This color must be
+         *                     opaque.
+         */
+        public TaskDescription(String label, @DrawableRes int iconRes, int colorPrimary) {
+            this(label, null, iconRes, null, colorPrimary, 0, 0, 0, false, false);
+            if ((colorPrimary != 0) && (Color.alpha(colorPrimary) != 255)) {
+                throw new RuntimeException("A TaskDescription's primary color should be opaque");
+            }
+        }
+
+        /**
+         * Creates the TaskDescription to the specified values.
+         *
+         * @param label A label and description of the current state of this activity.
+         * @param icon An icon that represents the current state of this activity.
+         * @deprecated use TaskDescription constructor with icon resource instead
+         */
+        @Deprecated
+        public TaskDescription(String label, Bitmap icon) {
+            this(label, icon, 0, null, 0, 0, 0, 0, false, false);
+        }
+
+        /**
+         * Creates the TaskDescription to the specified values.
+         *
+         * @param label A label and description of the current state of this activity.
+         * @param iconRes A drawable resource of an icon that represents the current state of this
+         *                activity.
+         */
+        public TaskDescription(String label, @DrawableRes int iconRes) {
+            this(label, null, iconRes, null, 0, 0, 0, 0, false, false);
+        }
+
+        /**
+         * Creates the TaskDescription to the specified values.
+         *
+         * @param label A label and description of the current state of this activity.
+         */
+        public TaskDescription(String label) {
+            this(label, null, 0, null, 0, 0, 0, 0, false, false);
+        }
+
+        /**
+         * Creates an empty TaskDescription.
+         */
+        public TaskDescription() {
+            this(null, null, 0, null, 0, 0, 0, 0, false, false);
+        }
+
+        /** @hide */
+        public TaskDescription(String label, Bitmap bitmap, int iconRes, String iconFilename,
+                int colorPrimary, int colorBackground, int statusBarColor, int navigationBarColor,
+                boolean ensureStatusBarContrastWhenTransparent,
+                boolean ensureNavigationBarContrastWhenTransparent) {
+            mLabel = label;
+            mIcon = bitmap;
+            mIconRes = iconRes;
+            mIconFilename = iconFilename;
+            mColorPrimary = colorPrimary;
+            mColorBackground = colorBackground;
+            mStatusBarColor = statusBarColor;
+            mNavigationBarColor = navigationBarColor;
+            mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
+            mEnsureNavigationBarContrastWhenTransparent =
+                    ensureNavigationBarContrastWhenTransparent;
+        }
+
+        /**
+         * Creates a copy of another TaskDescription.
+         */
+        public TaskDescription(TaskDescription td) {
+            copyFrom(td);
+        }
+
+        /**
+         * Copies this the values from another TaskDescription.
+         * @hide
+         */
+        public void copyFrom(TaskDescription other) {
+            mLabel = other.mLabel;
+            mIcon = other.mIcon;
+            mIconRes = other.mIconRes;
+            mIconFilename = other.mIconFilename;
+            mColorPrimary = other.mColorPrimary;
+            mColorBackground = other.mColorBackground;
+            mStatusBarColor = other.mStatusBarColor;
+            mNavigationBarColor = other.mNavigationBarColor;
+            mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
+            mEnsureNavigationBarContrastWhenTransparent =
+                    other.mEnsureNavigationBarContrastWhenTransparent;
+        }
+
+        /**
+         * Copies this the values from another TaskDescription, but preserves the hidden fields
+         * if they weren't set on {@code other}
+         * @hide
+         */
+        public void copyFromPreserveHiddenFields(TaskDescription other) {
+            mLabel = other.mLabel;
+            mIcon = other.mIcon;
+            mIconRes = other.mIconRes;
+            mIconFilename = other.mIconFilename;
+            mColorPrimary = other.mColorPrimary;
+            if (other.mColorBackground != 0) {
+                mColorBackground = other.mColorBackground;
+            }
+            if (other.mStatusBarColor != 0) {
+                mStatusBarColor = other.mStatusBarColor;
+            }
+            if (other.mNavigationBarColor != 0) {
+                mNavigationBarColor = other.mNavigationBarColor;
+            }
+            mEnsureStatusBarContrastWhenTransparent = other.mEnsureStatusBarContrastWhenTransparent;
+            mEnsureNavigationBarContrastWhenTransparent =
+                    other.mEnsureNavigationBarContrastWhenTransparent;
+        }
+
+        private TaskDescription(Parcel source) {
+            readFromParcel(source);
+        }
+
+        /**
+         * Sets the label for this task description.
+         * @hide
+         */
+        public void setLabel(String label) {
+            mLabel = label;
+        }
+
+        /**
+         * Sets the primary color for this task description.
+         * @hide
+         */
+        public void setPrimaryColor(int primaryColor) {
+            // Ensure that the given color is valid
+            if ((primaryColor != 0) && (Color.alpha(primaryColor) != 255)) {
+                throw new RuntimeException("A TaskDescription's primary color should be opaque");
+            }
+            mColorPrimary = primaryColor;
+        }
+
+        /**
+         * Sets the background color for this task description.
+         * @hide
+         */
+        public void setBackgroundColor(int backgroundColor) {
+            // Ensure that the given color is valid
+            if ((backgroundColor != 0) && (Color.alpha(backgroundColor) != 255)) {
+                throw new RuntimeException("A TaskDescription's background color should be opaque");
+            }
+            mColorBackground = backgroundColor;
+        }
+
+        /**
+         * @hide
+         */
+        public void setStatusBarColor(int statusBarColor) {
+            mStatusBarColor = statusBarColor;
+        }
+
+        /**
+         * @hide
+         */
+        public void setNavigationBarColor(int navigationBarColor) {
+            mNavigationBarColor = navigationBarColor;
+        }
+
+        /**
+         * Sets the icon for this task description.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public void setIcon(Bitmap icon) {
+            mIcon = icon;
+        }
+
+        /**
+         * Sets the icon resource for this task description.
+         * @hide
+         */
+        public void setIcon(int iconRes) {
+            mIconRes = iconRes;
+        }
+
+        /**
+         * Moves the icon bitmap reference from an actual Bitmap to a file containing the
+         * bitmap.
+         * @hide
+         */
+        public void setIconFilename(String iconFilename) {
+            mIconFilename = iconFilename;
+            mIcon = null;
+        }
+
+        /**
+         * @return The label and description of the current state of this task.
+         */
+        public String getLabel() {
+            return mLabel;
+        }
+
+        /**
+         * @return The icon that represents the current state of this task.
+         */
+        public Bitmap getIcon() {
+            if (mIcon != null) {
+                return mIcon;
+            }
+            return loadTaskDescriptionIcon(mIconFilename, UserHandle.myUserId());
+        }
+
+        /** @hide */
+        @TestApi
+        public int getIconResource() {
+            return mIconRes;
+        }
+
+        /** @hide */
+        @TestApi
+        public String getIconFilename() {
+            return mIconFilename;
+        }
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public Bitmap getInMemoryIcon() {
+            return mIcon;
+        }
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public static Bitmap loadTaskDescriptionIcon(String iconFilename, int userId) {
+            if (iconFilename != null) {
+                try {
+                    return getTaskService().getTaskDescriptionIcon(iconFilename,
+                            userId);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            return null;
+        }
+
+        /**
+         * @return The color override on the theme's primary color.
+         */
+        public int getPrimaryColor() {
+            return mColorPrimary;
+        }
+
+        /**
+         * @return The background color.
+         * @hide
+         */
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+        public int getBackgroundColor() {
+            return mColorBackground;
+        }
+
+        /**
+         * @hide
+         */
+        public int getStatusBarColor() {
+            return mStatusBarColor;
+        }
+
+        /**
+         * @hide
+         */
+        public int getNavigationBarColor() {
+            return mNavigationBarColor;
+        }
+
+        /**
+         * @hide
+         */
+        public boolean getEnsureStatusBarContrastWhenTransparent() {
+            return mEnsureStatusBarContrastWhenTransparent;
+        }
+
+        /**
+         * @hide
+         */
+        public void setEnsureStatusBarContrastWhenTransparent(
+                boolean ensureStatusBarContrastWhenTransparent) {
+            mEnsureStatusBarContrastWhenTransparent = ensureStatusBarContrastWhenTransparent;
+        }
+
+        /**
+         * @hide
+         */
+        public boolean getEnsureNavigationBarContrastWhenTransparent() {
+            return mEnsureNavigationBarContrastWhenTransparent;
+        }
+
+        /**
+         * @hide
+         */
+        public void setEnsureNavigationBarContrastWhenTransparent(
+                boolean ensureNavigationBarContrastWhenTransparent) {
+            mEnsureNavigationBarContrastWhenTransparent =
+                    ensureNavigationBarContrastWhenTransparent;
+        }
+
+        /** @hide */
+        public void saveToXml(XmlSerializer out) throws IOException {
+            if (mLabel != null) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONLABEL, mLabel);
+            }
+            if (mColorPrimary != 0) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR_PRIMARY,
+                        Integer.toHexString(mColorPrimary));
+            }
+            if (mColorBackground != 0) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND,
+                        Integer.toHexString(mColorBackground));
+            }
+            if (mIconFilename != null) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONICON_FILENAME, mIconFilename);
+            }
+            if (mIconRes != 0) {
+                out.attribute(null, ATTR_TASKDESCRIPTIONICON_RESOURCE, Integer.toString(mIconRes));
+            }
+        }
+
+        /** @hide */
+        public void restoreFromXml(String attrName, String attrValue) {
+            if (ATTR_TASKDESCRIPTIONLABEL.equals(attrName)) {
+                setLabel(attrValue);
+            } else if (ATTR_TASKDESCRIPTIONCOLOR_PRIMARY.equals(attrName)) {
+                setPrimaryColor((int) Long.parseLong(attrValue, 16));
+            } else if (ATTR_TASKDESCRIPTIONCOLOR_BACKGROUND.equals(attrName)) {
+                setBackgroundColor((int) Long.parseLong(attrValue, 16));
+            } else if (ATTR_TASKDESCRIPTIONICON_FILENAME.equals(attrName)) {
+                setIconFilename(attrValue);
+            } else if (ATTR_TASKDESCRIPTIONICON_RESOURCE.equals(attrName)) {
+                setIcon(Integer.parseInt(attrValue, 10));
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            if (mLabel == null) {
+                dest.writeInt(0);
+            } else {
+                dest.writeInt(1);
+                dest.writeString(mLabel);
+            }
+            if (mIcon == null) {
+                dest.writeInt(0);
+            } else {
+                dest.writeInt(1);
+                mIcon.writeToParcel(dest, 0);
+            }
+            dest.writeInt(mIconRes);
+            dest.writeInt(mColorPrimary);
+            dest.writeInt(mColorBackground);
+            dest.writeInt(mStatusBarColor);
+            dest.writeInt(mNavigationBarColor);
+            dest.writeBoolean(mEnsureStatusBarContrastWhenTransparent);
+            dest.writeBoolean(mEnsureNavigationBarContrastWhenTransparent);
+            if (mIconFilename == null) {
+                dest.writeInt(0);
+            } else {
+                dest.writeInt(1);
+                dest.writeString(mIconFilename);
+            }
+        }
+
+        public void readFromParcel(Parcel source) {
+            mLabel = source.readInt() > 0 ? source.readString() : null;
+            mIcon = source.readInt() > 0 ? Bitmap.CREATOR.createFromParcel(source) : null;
+            mIconRes = source.readInt();
+            mColorPrimary = source.readInt();
+            mColorBackground = source.readInt();
+            mStatusBarColor = source.readInt();
+            mNavigationBarColor = source.readInt();
+            mEnsureStatusBarContrastWhenTransparent = source.readBoolean();
+            mEnsureNavigationBarContrastWhenTransparent = source.readBoolean();
+            mIconFilename = source.readInt() > 0 ? source.readString() : null;
+        }
+
+        public static final @android.annotation.NonNull Creator<TaskDescription> CREATOR
+                = new Creator<TaskDescription>() {
+            public TaskDescription createFromParcel(Parcel source) {
+                return new TaskDescription(source);
+            }
+            public TaskDescription[] newArray(int size) {
+                return new TaskDescription[size];
+            }
+        };
+
+        @Override
+        public String toString() {
+            return "TaskDescription Label: " + mLabel + " Icon: " + mIcon +
+                    " IconRes: " + mIconRes + " IconFilename: " + mIconFilename +
+                    " colorPrimary: " + mColorPrimary + " colorBackground: " + mColorBackground +
+                    " statusBarColor: " + mStatusBarColor + (
+                    mEnsureStatusBarContrastWhenTransparent ? " (contrast when transparent)"
+                            : "") + " navigationBarColor: " + mNavigationBarColor + (
+                    mEnsureNavigationBarContrastWhenTransparent
+                            ? " (contrast when transparent)" : "");
+        }
+    }
+
+    /**
+     * Information you can retrieve about tasks that the user has most recently
+     * started or visited.
+     */
+    public static class RecentTaskInfo extends TaskInfo implements Parcelable {
+        /**
+         * If this task is currently running, this is the identifier for it.
+         * If it is not running, this will be -1.
+         *
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, use
+         * {@link RecentTaskInfo#taskId} to get the task id and {@link RecentTaskInfo#isRunning}
+         * to determine if it is running.
+         */
+        @Deprecated
+        public int id;
+
+        /**
+         * The true identifier of this task, valid even if it is not running.
+         *
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, use
+         * {@link RecentTaskInfo#taskId}.
+         */
+        @Deprecated
+        public int persistentId;
+
+        /**
+         * Description of the task's last state.
+         *
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, currently always null.
+         */
+        @Deprecated
+        public CharSequence description;
+
+        /**
+         * Task affiliation for grouping with other tasks.
+         *
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, currently always 0.
+         */
+        @Deprecated
+        public int affiliatedTaskId;
+
+        public RecentTaskInfo() {
+        }
+
+        private RecentTaskInfo(Parcel source) {
+            readFromParcel(source);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public void readFromParcel(Parcel source) {
+            id = source.readInt();
+            persistentId = source.readInt();
+            super.readFromParcel(source);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(id);
+            dest.writeInt(persistentId);
+            super.writeToParcel(dest, flags);
+        }
+
+        public static final @android.annotation.NonNull Creator<RecentTaskInfo> CREATOR
+                = new Creator<RecentTaskInfo>() {
+            public RecentTaskInfo createFromParcel(Parcel source) {
+                return new RecentTaskInfo(source);
+            }
+            public RecentTaskInfo[] newArray(int size) {
+                return new RecentTaskInfo[size];
+            }
+        };
+
+        /**
+         * @hide
+         */
+        public void dump(PrintWriter pw, String indent) {
+            final String activityType = WindowConfiguration.activityTypeToString(
+                    configuration.windowConfiguration.getActivityType());
+            final String windowingMode = WindowConfiguration.activityTypeToString(
+                    configuration.windowConfiguration.getActivityType());
+
+            pw.println(); pw.print("   ");
+            pw.print(" id=" + persistentId);
+            pw.print(" stackId=" + stackId);
+            pw.print(" userId=" + userId);
+            pw.print(" hasTask=" + (id != -1));
+            pw.print(" lastActiveTime=" + lastActiveTime);
+            pw.println(); pw.print("   ");
+            pw.print(" baseIntent=" + baseIntent);
+            pw.println(); pw.print("   ");
+            pw.print(" isExcluded="
+                    + ((baseIntent.getFlags() & FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0));
+            pw.print(" activityType=" + activityType);
+            pw.print(" windowingMode=" + windowingMode);
+            pw.print(" supportsSplitScreenMultiWindow=" + supportsSplitScreenMultiWindow);
+            if (taskDescription != null) {
+                pw.println(); pw.print("   ");
+                final ActivityManager.TaskDescription td = taskDescription;
+                pw.print(" taskDescription {");
+                pw.print(" colorBackground=#" + Integer.toHexString(td.getBackgroundColor()));
+                pw.print(" colorPrimary=#" + Integer.toHexString(td.getPrimaryColor()));
+                pw.print(" iconRes=" + (td.getIconResource() != 0));
+                pw.print(" iconBitmap=" + (td.getIconFilename() != null
+                        || td.getInMemoryIcon() != null));
+                pw.println(" }");
+            }
+        }
+    }
+
+    /**
+     * Flag for use with {@link #getRecentTasks}: return all tasks, even those
+     * that have set their
+     * {@link android.content.Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag.
+     */
+    public static final int RECENT_WITH_EXCLUDED = 0x0001;
+
+    /**
+     * Provides a list that does not contain any
+     * recent tasks that currently are not available to the user.
+     */
+    public static final int RECENT_IGNORE_UNAVAILABLE = 0x0002;
+
+    /**
+     * <p></p>Return a list of the tasks that the user has recently launched, with
+     * the most recent being first and older ones after in order.
+     *
+     * <p><b>Note: this method is only intended for debugging and presenting
+     * task management user interfaces</b>.  This should never be used for
+     * core logic in an application, such as deciding between different
+     * behaviors based on the information found here.  Such uses are
+     * <em>not</em> supported, and will likely break in the future.  For
+     * example, if multiple applications can be actively running at the
+     * same time, assumptions made about the meaning of the data here for
+     * purposes of control flow will be incorrect.</p>
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this method is
+     * no longer available to third party applications: the introduction of
+     * document-centric recents means
+     * it can leak personal information to the caller.  For backwards compatibility,
+     * it will still return a small subset of its data: at least the caller's
+     * own tasks (though see {@link #getAppTasks()} for the correct supported
+     * way to retrieve that information), and possibly some other tasks
+     * such as home that are known to not be sensitive.
+     *
+     * @param maxNum The maximum number of entries to return in the list.  The
+     * actual number returned may be smaller, depending on how many tasks the
+     * user has started and the maximum number the system can remember.
+     * @param flags Information about what to return.  May be any combination
+     * of {@link #RECENT_WITH_EXCLUDED} and {@link #RECENT_IGNORE_UNAVAILABLE}.
+     *
+     * @return Returns a list of RecentTaskInfo records describing each of
+     * the recent tasks.
+     */
+    @Deprecated
+    public List<RecentTaskInfo> getRecentTasks(int maxNum, int flags)
+            throws SecurityException {
+        try {
+            if (maxNum < 0) {
+                throw new IllegalArgumentException("The requested number of tasks should be >= 0");
+            }
+            return getTaskService().getRecentTasks(maxNum, flags, mContext.getUserId()).getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Information you can retrieve about a particular task that is currently
+     * "running" in the system.  Note that a running task does not mean the
+     * given task actually has a process it is actively running in; it simply
+     * means that the user has gone to it and never closed it, but currently
+     * the system may have killed its process and is only holding on to its
+     * last state in order to restart it when the user returns.
+     */
+    public static class RunningTaskInfo extends TaskInfo implements Parcelable {
+
+        /**
+         * A unique identifier for this task.
+         *
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, use
+         * {@link RunningTaskInfo#taskId}.
+         */
+        @Deprecated
+        public int id;
+
+        /**
+         * Thumbnail representation of the task's current state.
+         *
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, currently always null.
+         */
+        @Deprecated
+        public Bitmap thumbnail;
+
+        /**
+         * Description of the task's current state.
+         *
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, currently always null.
+         */
+        @Deprecated
+        public CharSequence description;
+
+        /**
+         * Number of activities that are currently running (not stopped and persisted) in this task.
+         *
+         * @deprecated As of {@link android.os.Build.VERSION_CODES#Q}, currently always 0.
+         */
+        @Deprecated
+        public int numRunning;
+
+        public RunningTaskInfo() {
+        }
+
+        private RunningTaskInfo(Parcel source) {
+            readFromParcel(source);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        public void readFromParcel(Parcel source) {
+            id = source.readInt();
+            super.readFromParcel(source);
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(id);
+            super.writeToParcel(dest, flags);
+        }
+
+        public static final @android.annotation.NonNull Creator<RunningTaskInfo> CREATOR = new Creator<RunningTaskInfo>() {
+            public RunningTaskInfo createFromParcel(Parcel source) {
+                return new RunningTaskInfo(source);
+            }
+            public RunningTaskInfo[] newArray(int size) {
+                return new RunningTaskInfo[size];
+            }
+        };
+    }
+
+    /**
+     * Get the list of tasks associated with the calling application.
+     *
+     * @return The list of tasks associated with the application making this call.
+     * @throws SecurityException
+     */
+    public List<ActivityManager.AppTask> getAppTasks() {
+        ArrayList<AppTask> tasks = new ArrayList<AppTask>();
+        List<IBinder> appTasks;
+        try {
+            appTasks = getTaskService().getAppTasks(mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        int numAppTasks = appTasks.size();
+        for (int i = 0; i < numAppTasks; i++) {
+            tasks.add(new AppTask(IAppTask.Stub.asInterface(appTasks.get(i))));
+        }
+        return tasks;
+    }
+
+    /**
+     * Return the current design dimensions for {@link AppTask} thumbnails, for use
+     * with {@link #addAppTask}.
+     */
+    public Size getAppTaskThumbnailSize() {
+        synchronized (this) {
+            ensureAppTaskThumbnailSizeLocked();
+            return new Size(mAppTaskThumbnailSize.x, mAppTaskThumbnailSize.y);
+        }
+    }
+
+    private void ensureAppTaskThumbnailSizeLocked() {
+        if (mAppTaskThumbnailSize == null) {
+            try {
+                mAppTaskThumbnailSize = getTaskService().getAppTaskThumbnailSize();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Add a new {@link AppTask} for the calling application.  This will create a new
+     * recents entry that is added to the <b>end</b> of all existing recents.
+     *
+     * @param activity The activity that is adding the entry.   This is used to help determine
+     * the context that the new recents entry will be in.
+     * @param intent The Intent that describes the recents entry.  This is the same Intent that
+     * you would have used to launch the activity for it.  In generally you will want to set
+     * both {@link Intent#FLAG_ACTIVITY_NEW_DOCUMENT} and
+     * {@link Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}; the latter is required since this recents
+     * entry will exist without an activity, so it doesn't make sense to not retain it when
+     * its activity disappears.  The given Intent here also must have an explicit ComponentName
+     * set on it.
+     * @param description Optional additional description information.
+     * @param thumbnail Thumbnail to use for the recents entry.  Should be the size given by
+     * {@link #getAppTaskThumbnailSize()}.  If the bitmap is not that exact size, it will be
+     * recreated in your process, probably in a way you don't like, before the recents entry
+     * is added.
+     *
+     * @return Returns the task id of the newly added app task, or -1 if the add failed.  The
+     * most likely cause of failure is that there is no more room for more tasks for your app.
+     */
+    public int addAppTask(@NonNull Activity activity, @NonNull Intent intent,
+            @Nullable TaskDescription description, @NonNull Bitmap thumbnail) {
+        Point size;
+        synchronized (this) {
+            ensureAppTaskThumbnailSizeLocked();
+            size = mAppTaskThumbnailSize;
+        }
+        final int tw = thumbnail.getWidth();
+        final int th = thumbnail.getHeight();
+        if (tw != size.x || th != size.y) {
+            Bitmap bm = Bitmap.createBitmap(size.x, size.y, thumbnail.getConfig());
+
+            // Use ScaleType.CENTER_CROP, except we leave the top edge at the top.
+            float scale;
+            float dx = 0, dy = 0;
+            if (tw * size.x > size.y * th) {
+                scale = (float) size.x / (float) th;
+                dx = (size.y - tw * scale) * 0.5f;
+            } else {
+                scale = (float) size.y / (float) tw;
+                dy = (size.x - th * scale) * 0.5f;
+            }
+            Matrix matrix = new Matrix();
+            matrix.setScale(scale, scale);
+            matrix.postTranslate((int) (dx + 0.5f), 0);
+
+            Canvas canvas = new Canvas(bm);
+            canvas.drawBitmap(thumbnail, matrix, null);
+            canvas.setBitmap(null);
+
+            thumbnail = bm;
+        }
+        if (description == null) {
+            description = new TaskDescription();
+        }
+        try {
+            return getTaskService().addAppTask(activity.getActivityToken(),
+                    intent, description, thumbnail);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return a list of the tasks that are currently running, with
+     * the most recent being first and older ones after in order.  Note that
+     * "running" does not mean any of the task's code is currently loaded or
+     * activity -- the task may have been frozen by the system, so that it
+     * can be restarted in its previous state when next brought to the
+     * foreground.
+     *
+     * <p><b>Note: this method is only intended for debugging and presenting
+     * task management user interfaces</b>.  This should never be used for
+     * core logic in an application, such as deciding between different
+     * behaviors based on the information found here.  Such uses are
+     * <em>not</em> supported, and will likely break in the future.  For
+     * example, if multiple applications can be actively running at the
+     * same time, assumptions made about the meaning of the data here for
+     * purposes of control flow will be incorrect.</p>
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this method
+     * is no longer available to third party
+     * applications: the introduction of document-centric recents means
+     * it can leak person information to the caller.  For backwards compatibility,
+     * it will still return a small subset of its data: at least the caller's
+     * own tasks, and possibly some other tasks
+     * such as home that are known to not be sensitive.
+     *
+     * @param maxNum The maximum number of entries to return in the list.  The
+     * actual number returned may be smaller, depending on how many tasks the
+     * user has started.
+     *
+     * @return Returns a list of RunningTaskInfo records describing each of
+     * the running tasks.
+     */
+    @Deprecated
+    public List<RunningTaskInfo> getRunningTasks(int maxNum)
+            throws SecurityException {
+        try {
+            return getTaskService().getTasks(maxNum);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Represents a task snapshot.
+     * @hide
+     */
+    public static class TaskSnapshot implements Parcelable {
+
+        // Top activity in task when snapshot was taken
+        private final ComponentName mTopActivityComponent;
+        private final GraphicBuffer mSnapshot;
+        private final int mOrientation;
+        private final Rect mContentInsets;
+        // Whether this snapshot is a down-sampled version of the full resolution, used mainly for
+        // low-ram devices
+        private final boolean mReducedResolution;
+        // Whether or not the snapshot is a real snapshot or an app-theme generated snapshot due to
+        // the task having a secure window or having previews disabled
+        private final boolean mIsRealSnapshot;
+        private final int mWindowingMode;
+        private final float mScale;
+        private final int mSystemUiVisibility;
+        private final boolean mIsTranslucent;
+        // Must be one of the named color spaces, otherwise, always use SRGB color space.
+        private final ColorSpace mColorSpace;
+
+        public TaskSnapshot(@NonNull ComponentName topActivityComponent, GraphicBuffer snapshot,
+                @NonNull ColorSpace colorSpace, int orientation, Rect contentInsets,
+                boolean reducedResolution, float scale, boolean isRealSnapshot, int windowingMode,
+                int systemUiVisibility, boolean isTranslucent) {
+            mTopActivityComponent = topActivityComponent;
+            mSnapshot = snapshot;
+            mColorSpace = colorSpace.getId() < 0
+                    ? ColorSpace.get(ColorSpace.Named.SRGB) : colorSpace;
+            mOrientation = orientation;
+            mContentInsets = new Rect(contentInsets);
+            mReducedResolution = reducedResolution;
+            mScale = scale;
+            mIsRealSnapshot = isRealSnapshot;
+            mWindowingMode = windowingMode;
+            mSystemUiVisibility = systemUiVisibility;
+            mIsTranslucent = isTranslucent;
+        }
+
+        private TaskSnapshot(Parcel source) {
+            mTopActivityComponent = ComponentName.readFromParcel(source);
+            mSnapshot = source.readParcelable(null /* classLoader */);
+            int colorSpaceId = source.readInt();
+            mColorSpace = colorSpaceId >= 0 && colorSpaceId < ColorSpace.Named.values().length
+                    ? ColorSpace.get(ColorSpace.Named.values()[colorSpaceId])
+                    : ColorSpace.get(ColorSpace.Named.SRGB);
+            mOrientation = source.readInt();
+            mContentInsets = source.readParcelable(null /* classLoader */);
+            mReducedResolution = source.readBoolean();
+            mScale = source.readFloat();
+            mIsRealSnapshot = source.readBoolean();
+            mWindowingMode = source.readInt();
+            mSystemUiVisibility = source.readInt();
+            mIsTranslucent = source.readBoolean();
+        }
+
+        /**
+         * @return The top activity component for the task at the point this snapshot was taken.
+         */
+        public ComponentName getTopActivityComponent() {
+            return mTopActivityComponent;
+        }
+
+        /**
+         * @return The graphic buffer representing the screenshot.
+         */
+        @UnsupportedAppUsage
+        public GraphicBuffer getSnapshot() {
+            return mSnapshot;
+        }
+
+        /**
+         * @return The color space of graphic buffer representing the screenshot.
+         */
+        public ColorSpace getColorSpace() {
+            return mColorSpace;
+        }
+
+        /**
+         * @return The screen orientation the screenshot was taken in.
+         */
+        @UnsupportedAppUsage
+        public int getOrientation() {
+            return mOrientation;
+        }
+
+        /**
+         * @return The system/content insets on the snapshot. These can be clipped off in order to
+         *         remove any areas behind system bars in the snapshot.
+         */
+        @UnsupportedAppUsage
+        public Rect getContentInsets() {
+            return mContentInsets;
+        }
+
+        /**
+         * @return Whether this snapshot is a down-sampled version of the full resolution.
+         */
+        @UnsupportedAppUsage
+        public boolean isReducedResolution() {
+            return mReducedResolution;
+        }
+
+        /**
+         * @return Whether or not the snapshot is a real snapshot or an app-theme generated snapshot
+         * due to the task having a secure window or having previews disabled.
+         */
+        @UnsupportedAppUsage
+        public boolean isRealSnapshot() {
+            return mIsRealSnapshot;
+        }
+
+        /**
+         * @return Whether or not the snapshot is of a translucent app window (non-fullscreen or has
+         * a non-opaque pixel format).
+         */
+        public boolean isTranslucent() {
+            return mIsTranslucent;
+        }
+
+        /**
+         * @return The windowing mode of the task when this snapshot was taken.
+         */
+        public int getWindowingMode() {
+            return mWindowingMode;
+        }
+
+        /**
+         * @return The system ui visibility flags for the top most visible fullscreen window at the
+         *         time that the snapshot was taken.
+         */
+        public int getSystemUiVisibility() {
+            return mSystemUiVisibility;
+        }
+
+        /**
+         * @return The scale this snapshot was taken in.
+         */
+        @UnsupportedAppUsage
+        public float getScale() {
+            return mScale;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            ComponentName.writeToParcel(mTopActivityComponent, dest);
+            dest.writeParcelable(mSnapshot, 0);
+            dest.writeInt(mColorSpace.getId());
+            dest.writeInt(mOrientation);
+            dest.writeParcelable(mContentInsets, 0);
+            dest.writeBoolean(mReducedResolution);
+            dest.writeFloat(mScale);
+            dest.writeBoolean(mIsRealSnapshot);
+            dest.writeInt(mWindowingMode);
+            dest.writeInt(mSystemUiVisibility);
+            dest.writeBoolean(mIsTranslucent);
+        }
+
+        @Override
+        public String toString() {
+            final int width = mSnapshot != null ? mSnapshot.getWidth() : 0;
+            final int height = mSnapshot != null ? mSnapshot.getHeight() : 0;
+            return "TaskSnapshot{"
+                    + " mTopActivityComponent=" + mTopActivityComponent.flattenToShortString()
+                    + " mSnapshot=" + mSnapshot + " (" + width + "x" + height + ")"
+                    + " mColorSpace=" + mColorSpace.toString()
+                    + " mOrientation=" + mOrientation
+                    + " mContentInsets=" + mContentInsets.toShortString()
+                    + " mReducedResolution=" + mReducedResolution + " mScale=" + mScale
+                    + " mIsRealSnapshot=" + mIsRealSnapshot + " mWindowingMode=" + mWindowingMode
+                    + " mSystemUiVisibility=" + mSystemUiVisibility
+                    + " mIsTranslucent=" + mIsTranslucent;
+        }
+
+        public static final @android.annotation.NonNull Creator<TaskSnapshot> CREATOR = new Creator<TaskSnapshot>() {
+            public TaskSnapshot createFromParcel(Parcel source) {
+                return new TaskSnapshot(source);
+            }
+            public TaskSnapshot[] newArray(int size) {
+                return new TaskSnapshot[size];
+            }
+        };
+    }
+
+    /** @hide */
+    @IntDef(flag = true, prefix = { "MOVE_TASK_" }, value = {
+            MOVE_TASK_WITH_HOME,
+            MOVE_TASK_NO_USER_ACTION,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface MoveTaskFlags {}
+
+    /**
+     * Flag for {@link #moveTaskToFront(int, int)}: also move the "home"
+     * activity along with the task, so it is positioned immediately behind
+     * the task.
+     */
+    public static final int MOVE_TASK_WITH_HOME = 0x00000001;
+
+    /**
+     * Flag for {@link #moveTaskToFront(int, int)}: don't count this as a
+     * user-instigated action, so the current activity will not receive a
+     * hint that the user is leaving.
+     */
+    public static final int MOVE_TASK_NO_USER_ACTION = 0x00000002;
+
+    /**
+     * Equivalent to calling {@link #moveTaskToFront(int, int, Bundle)}
+     * with a null options argument.
+     *
+     * @param taskId The identifier of the task to be moved, as found in
+     * {@link RunningTaskInfo} or {@link RecentTaskInfo}.
+     * @param flags Additional operational flags.
+     */
+    @RequiresPermission(android.Manifest.permission.REORDER_TASKS)
+    public void moveTaskToFront(int taskId, @MoveTaskFlags int flags) {
+        moveTaskToFront(taskId, flags, null);
+    }
+
+    /**
+     * Ask that the task associated with a given task ID be moved to the
+     * front of the stack, so it is now visible to the user.
+     *
+     * @param taskId The identifier of the task to be moved, as found in
+     * {@link RunningTaskInfo} or {@link RecentTaskInfo}.
+     * @param flags Additional operational flags.
+     * @param options Additional options for the operation, either null or
+     * as per {@link Context#startActivity(Intent, android.os.Bundle)
+     * Context.startActivity(Intent, Bundle)}.
+     */
+    @RequiresPermission(android.Manifest.permission.REORDER_TASKS)
+    public void moveTaskToFront(int taskId, @MoveTaskFlags int flags, Bundle options) {
+        try {
+            ActivityThread thread = ActivityThread.currentActivityThread();
+            IApplicationThread appThread = thread.getApplicationThread();
+            String packageName = mContext.getPackageName();
+            getTaskService().moveTaskToFront(appThread, packageName, taskId, flags, options);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Check if the context is allowed to start an activity on specified display. Some launch
+     * restrictions may apply to secondary displays that are private, virtual, or owned by the
+     * system, in which case an activity start may throw a {@link SecurityException}. Call this
+     * method prior to starting an activity on a secondary display to check if the current context
+     * has access to it.
+     *
+     * @see ActivityOptions#setLaunchDisplayId(int)
+     * @see android.view.Display.FLAG_PRIVATE
+     * @see android.view.Display.TYPE_VIRTUAL
+     *
+     * @param context Source context, from which an activity will be started.
+     * @param displayId Target display id.
+     * @param intent Intent used to launch an activity.
+     * @return {@code true} if a call to start an activity on the target display is allowed for the
+     * provided context and no {@link SecurityException} will be thrown, {@code false} otherwise.
+     */
+    public boolean isActivityStartAllowedOnDisplay(@NonNull Context context, int displayId,
+            @NonNull Intent intent) {
+        try {
+            return getTaskService().isActivityStartAllowedOnDisplay(displayId, intent,
+                    intent.resolveTypeIfNeeded(context.getContentResolver()), context.getUserId());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+        return false;
+    }
+
+    /**
+     * Information you can retrieve about a particular Service that is
+     * currently running in the system.
+     */
+    public static class RunningServiceInfo implements Parcelable {
+        /**
+         * The service component.
+         */
+        public ComponentName service;
+
+        /**
+         * If non-zero, this is the process the service is running in.
+         */
+        public int pid;
+
+        /**
+         * The UID that owns this service.
+         */
+        public int uid;
+
+        /**
+         * The name of the process this service runs in.
+         */
+        public String process;
+
+        /**
+         * Set to true if the service has asked to run as a foreground process.
+         */
+        public boolean foreground;
+
+        /**
+         * The time when the service was first made active, either by someone
+         * starting or binding to it.  This
+         * is in units of {@link android.os.SystemClock#elapsedRealtime()}.
+         */
+        public long activeSince;
+
+        /**
+         * Set to true if this service has been explicitly started.
+         */
+        public boolean started;
+
+        /**
+         * Number of clients connected to the service.
+         */
+        public int clientCount;
+
+        /**
+         * Number of times the service's process has crashed while the service
+         * is running.
+         */
+        public int crashCount;
+
+        /**
+         * The time when there was last activity in the service (either
+         * explicit requests to start it or clients binding to it).  This
+         * is in units of {@link android.os.SystemClock#uptimeMillis()}.
+         */
+        public long lastActivityTime;
+
+        /**
+         * If non-zero, this service is not currently running, but scheduled to
+         * restart at the given time.
+         */
+        public long restarting;
+
+        /**
+         * Bit for {@link #flags}: set if this service has been
+         * explicitly started.
+         */
+        public static final int FLAG_STARTED = 1<<0;
+
+        /**
+         * Bit for {@link #flags}: set if the service has asked to
+         * run as a foreground process.
+         */
+        public static final int FLAG_FOREGROUND = 1<<1;
+
+        /**
+         * Bit for {@link #flags}: set if the service is running in a
+         * core system process.
+         */
+        public static final int FLAG_SYSTEM_PROCESS = 1<<2;
+
+        /**
+         * Bit for {@link #flags}: set if the service is running in a
+         * persistent process.
+         */
+        public static final int FLAG_PERSISTENT_PROCESS = 1<<3;
+
+        /**
+         * Running flags.
+         */
+        public int flags;
+
+        /**
+         * For special services that are bound to by system code, this is
+         * the package that holds the binding.
+         */
+        public String clientPackage;
+
+        /**
+         * For special services that are bound to by system code, this is
+         * a string resource providing a user-visible label for who the
+         * client is.
+         */
+        public int clientLabel;
+
+        public RunningServiceInfo() {
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            ComponentName.writeToParcel(service, dest);
+            dest.writeInt(pid);
+            dest.writeInt(uid);
+            dest.writeString(process);
+            dest.writeInt(foreground ? 1 : 0);
+            dest.writeLong(activeSince);
+            dest.writeInt(started ? 1 : 0);
+            dest.writeInt(clientCount);
+            dest.writeInt(crashCount);
+            dest.writeLong(lastActivityTime);
+            dest.writeLong(restarting);
+            dest.writeInt(this.flags);
+            dest.writeString(clientPackage);
+            dest.writeInt(clientLabel);
+        }
+
+        public void readFromParcel(Parcel source) {
+            service = ComponentName.readFromParcel(source);
+            pid = source.readInt();
+            uid = source.readInt();
+            process = source.readString();
+            foreground = source.readInt() != 0;
+            activeSince = source.readLong();
+            started = source.readInt() != 0;
+            clientCount = source.readInt();
+            crashCount = source.readInt();
+            lastActivityTime = source.readLong();
+            restarting = source.readLong();
+            flags = source.readInt();
+            clientPackage = source.readString();
+            clientLabel = source.readInt();
+        }
+
+        public static final @android.annotation.NonNull Creator<RunningServiceInfo> CREATOR = new Creator<RunningServiceInfo>() {
+            public RunningServiceInfo createFromParcel(Parcel source) {
+                return new RunningServiceInfo(source);
+            }
+            public RunningServiceInfo[] newArray(int size) {
+                return new RunningServiceInfo[size];
+            }
+        };
+
+        private RunningServiceInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+
+    /**
+     * Return a list of the services that are currently running.
+     *
+     * <p><b>Note: this method is only intended for debugging or implementing
+     * service management type user interfaces.</b></p>
+     *
+     * @deprecated As of {@link android.os.Build.VERSION_CODES#O}, this method
+     * is no longer available to third party applications.  For backwards compatibility,
+     * it will still return the caller's own services.
+     *
+     * @param maxNum The maximum number of entries to return in the list.  The
+     * actual number returned may be smaller, depending on how many services
+     * are running.
+     *
+     * @return Returns a list of RunningServiceInfo records describing each of
+     * the running tasks.
+     */
+    @Deprecated
+    public List<RunningServiceInfo> getRunningServices(int maxNum)
+            throws SecurityException {
+        try {
+            return getService()
+                    .getServices(maxNum, 0);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a PendingIntent you can start to show a control panel for the
+     * given running service.  If the service does not have a control panel,
+     * null is returned.
+     */
+    public PendingIntent getRunningServiceControlPanel(ComponentName service)
+            throws SecurityException {
+        try {
+            return getService()
+                    .getRunningServiceControlPanel(service);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Information you can retrieve about the available memory through
+     * {@link ActivityManager#getMemoryInfo}.
+     */
+    public static class MemoryInfo implements Parcelable {
+        /**
+         * The available memory on the system.  This number should not
+         * be considered absolute: due to the nature of the kernel, a significant
+         * portion of this memory is actually in use and needed for the overall
+         * system to run well.
+         */
+        public long availMem;
+
+        /**
+         * The total memory accessible by the kernel.  This is basically the
+         * RAM size of the device, not including below-kernel fixed allocations
+         * like DMA buffers, RAM for the baseband CPU, etc.
+         */
+        public long totalMem;
+
+        /**
+         * The threshold of {@link #availMem} at which we consider memory to be
+         * low and start killing background services and other non-extraneous
+         * processes.
+         */
+        public long threshold;
+
+        /**
+         * Set to true if the system considers itself to currently be in a low
+         * memory situation.
+         */
+        public boolean lowMemory;
+
+        /** @hide */
+        @UnsupportedAppUsage
+        public long hiddenAppThreshold;
+        /** @hide */
+        @UnsupportedAppUsage
+        public long secondaryServerThreshold;
+        /** @hide */
+        @UnsupportedAppUsage
+        public long visibleAppThreshold;
+        /** @hide */
+        @UnsupportedAppUsage
+        public long foregroundAppThreshold;
+
+        public MemoryInfo() {
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(availMem);
+            dest.writeLong(totalMem);
+            dest.writeLong(threshold);
+            dest.writeInt(lowMemory ? 1 : 0);
+            dest.writeLong(hiddenAppThreshold);
+            dest.writeLong(secondaryServerThreshold);
+            dest.writeLong(visibleAppThreshold);
+            dest.writeLong(foregroundAppThreshold);
+        }
+
+        public void readFromParcel(Parcel source) {
+            availMem = source.readLong();
+            totalMem = source.readLong();
+            threshold = source.readLong();
+            lowMemory = source.readInt() != 0;
+            hiddenAppThreshold = source.readLong();
+            secondaryServerThreshold = source.readLong();
+            visibleAppThreshold = source.readLong();
+            foregroundAppThreshold = source.readLong();
+        }
+
+        public static final @android.annotation.NonNull Creator<MemoryInfo> CREATOR
+                = new Creator<MemoryInfo>() {
+            public MemoryInfo createFromParcel(Parcel source) {
+                return new MemoryInfo(source);
+            }
+            public MemoryInfo[] newArray(int size) {
+                return new MemoryInfo[size];
+            }
+        };
+
+        private MemoryInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+
+    /**
+     * Return general information about the memory state of the system.  This
+     * can be used to help decide how to manage your own memory, though note
+     * that polling is not recommended and
+     * {@link android.content.ComponentCallbacks2#onTrimMemory(int)
+     * ComponentCallbacks2.onTrimMemory(int)} is the preferred way to do this.
+     * Also see {@link #getMyMemoryState} for how to retrieve the current trim
+     * level of your process as needed, which gives a better hint for how to
+     * manage its memory.
+     */
+    public void getMemoryInfo(MemoryInfo outInfo) {
+        try {
+            getService().getMemoryInfo(outInfo);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Information you can retrieve about an ActivityStack in the system.
+     * @hide
+     */
+    public static class StackInfo implements Parcelable {
+        @UnsupportedAppUsage
+        public int stackId;
+        @UnsupportedAppUsage
+        public Rect bounds = new Rect();
+        @UnsupportedAppUsage
+        public int[] taskIds;
+        @UnsupportedAppUsage
+        public String[] taskNames;
+        @UnsupportedAppUsage
+        public Rect[] taskBounds;
+        @UnsupportedAppUsage
+        public int[] taskUserIds;
+        @UnsupportedAppUsage
+        public ComponentName topActivity;
+        @UnsupportedAppUsage
+        public int displayId;
+        @UnsupportedAppUsage
+        public int userId;
+        @UnsupportedAppUsage
+        public boolean visible;
+        // Index of the stack in the display's stack list, can be used for comparison of stack order
+        @UnsupportedAppUsage
+        public int position;
+        /**
+         * The full configuration the stack is currently running in.
+         * @hide
+         */
+        final public Configuration configuration = new Configuration();
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(stackId);
+            dest.writeInt(bounds.left);
+            dest.writeInt(bounds.top);
+            dest.writeInt(bounds.right);
+            dest.writeInt(bounds.bottom);
+            dest.writeIntArray(taskIds);
+            dest.writeStringArray(taskNames);
+            final int boundsCount = taskBounds == null ? 0 : taskBounds.length;
+            dest.writeInt(boundsCount);
+            for (int i = 0; i < boundsCount; i++) {
+                dest.writeInt(taskBounds[i].left);
+                dest.writeInt(taskBounds[i].top);
+                dest.writeInt(taskBounds[i].right);
+                dest.writeInt(taskBounds[i].bottom);
+            }
+            dest.writeIntArray(taskUserIds);
+            dest.writeInt(displayId);
+            dest.writeInt(userId);
+            dest.writeInt(visible ? 1 : 0);
+            dest.writeInt(position);
+            if (topActivity != null) {
+                dest.writeInt(1);
+                topActivity.writeToParcel(dest, 0);
+            } else {
+                dest.writeInt(0);
+            }
+            configuration.writeToParcel(dest, flags);
+        }
+
+        public void readFromParcel(Parcel source) {
+            stackId = source.readInt();
+            bounds = new Rect(
+                    source.readInt(), source.readInt(), source.readInt(), source.readInt());
+            taskIds = source.createIntArray();
+            taskNames = source.createStringArray();
+            final int boundsCount = source.readInt();
+            if (boundsCount > 0) {
+                taskBounds = new Rect[boundsCount];
+                for (int i = 0; i < boundsCount; i++) {
+                    taskBounds[i] = new Rect();
+                    taskBounds[i].set(
+                            source.readInt(), source.readInt(), source.readInt(), source.readInt());
+                }
+            } else {
+                taskBounds = null;
+            }
+            taskUserIds = source.createIntArray();
+            displayId = source.readInt();
+            userId = source.readInt();
+            visible = source.readInt() > 0;
+            position = source.readInt();
+            if (source.readInt() > 0) {
+                topActivity = ComponentName.readFromParcel(source);
+            }
+            configuration.readFromParcel(source);
+        }
+
+        public static final @android.annotation.NonNull Creator<StackInfo> CREATOR = new Creator<StackInfo>() {
+            @Override
+            public StackInfo createFromParcel(Parcel source) {
+                return new StackInfo(source);
+            }
+            @Override
+            public StackInfo[] newArray(int size) {
+                return new StackInfo[size];
+            }
+        };
+
+        public StackInfo() {
+        }
+
+        private StackInfo(Parcel source) {
+            readFromParcel(source);
+        }
+
+        @UnsupportedAppUsage
+        public String toString(String prefix) {
+            StringBuilder sb = new StringBuilder(256);
+            sb.append(prefix); sb.append("Stack id="); sb.append(stackId);
+                    sb.append(" bounds="); sb.append(bounds.toShortString());
+                    sb.append(" displayId="); sb.append(displayId);
+                    sb.append(" userId="); sb.append(userId);
+                    sb.append("\n");
+                    sb.append(" configuration="); sb.append(configuration);
+                    sb.append("\n");
+            prefix = prefix + "  ";
+            for (int i = 0; i < taskIds.length; ++i) {
+                sb.append(prefix); sb.append("taskId="); sb.append(taskIds[i]);
+                        sb.append(": "); sb.append(taskNames[i]);
+                        if (taskBounds != null) {
+                            sb.append(" bounds="); sb.append(taskBounds[i].toShortString());
+                        }
+                        sb.append(" userId=").append(taskUserIds[i]);
+                        sb.append(" visible=").append(visible);
+                        if (topActivity != null) {
+                            sb.append(" topActivity=").append(topActivity);
+                        }
+                        sb.append("\n");
+            }
+            return sb.toString();
+        }
+
+        @Override
+        public String toString() {
+            return toString("");
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @RequiresPermission(anyOf={Manifest.permission.CLEAR_APP_USER_DATA,
+            Manifest.permission.ACCESS_INSTANT_APPS})
+    @UnsupportedAppUsage
+    public boolean clearApplicationUserData(String packageName, IPackageDataObserver observer) {
+        try {
+            return getService().clearApplicationUserData(packageName, false,
+                    observer, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Permits an application to erase its own data from disk.  This is equivalent to
+     * the user choosing to clear the app's data from within the device settings UI.  It
+     * erases all dynamic data associated with the app -- its private data and data in its
+     * private area on external storage -- but does not remove the installed application
+     * itself, nor any OBB files. It also revokes all runtime permissions that the app has acquired,
+     * clears all notifications and removes all Uri grants related to this application.
+     *
+     * @return {@code true} if the application successfully requested that the application's
+     *     data be erased; {@code false} otherwise.
+     */
+    public boolean clearApplicationUserData() {
+        return clearApplicationUserData(mContext.getPackageName(), null);
+    }
+
+    /**
+     * Permits an application to get the persistent URI permissions granted to another.
+     *
+     * <p>Typically called by Settings or DocumentsUI, requires
+     * {@code GET_APP_GRANTED_URI_PERMISSIONS}.
+     *
+     * @param packageName application to look for the granted permissions, or {@code null} to get
+     * granted permissions for all applications
+     * @return list of granted URI permissions
+     *
+     * @hide
+     * @deprecated use {@link UriGrantsManager#getGrantedUriPermissions(String)} instead.
+     */
+    @Deprecated
+    public ParceledListSlice<GrantedUriPermission> getGrantedUriPermissions(
+            @Nullable String packageName) {
+        return ((UriGrantsManager) mContext.getSystemService(Context.URI_GRANTS_SERVICE))
+                .getGrantedUriPermissions(packageName);
+    }
+
+    /**
+     * Permits an application to clear the persistent URI permissions granted to another.
+     *
+     * <p>Typically called by Settings, requires {@code CLEAR_APP_GRANTED_URI_PERMISSIONS}.
+     *
+     * @param packageName application to clear its granted permissions
+     *
+     * @hide
+     * @deprecated use {@link UriGrantsManager#clearGrantedUriPermissions(String)} instead.
+     */
+    @Deprecated
+    public void clearGrantedUriPermissions(String packageName) {
+        ((UriGrantsManager) mContext.getSystemService(Context.URI_GRANTS_SERVICE))
+                .clearGrantedUriPermissions(packageName);
+    }
+
+    /**
+     * Information you can retrieve about any processes that are in an error condition.
+     */
+    public static class ProcessErrorStateInfo implements Parcelable {
+        /**
+         * Condition codes
+         */
+        public static final int NO_ERROR = 0;
+        public static final int CRASHED = 1;
+        public static final int NOT_RESPONDING = 2;
+
+        /**
+         * The condition that the process is in.
+         */
+        public int condition;
+
+        /**
+         * The process name in which the crash or error occurred.
+         */
+        public String processName;
+
+        /**
+         * The pid of this process; 0 if none
+         */
+        public int pid;
+
+        /**
+         * The kernel user-ID that has been assigned to this process;
+         * currently this is not a unique ID (multiple applications can have
+         * the same uid).
+         */
+        public int uid;
+
+        /**
+         * The activity name associated with the error, if known.  May be null.
+         */
+        public String tag;
+
+        /**
+         * A short message describing the error condition.
+         */
+        public String shortMsg;
+
+        /**
+         * A long message describing the error condition.
+         */
+        public String longMsg;
+
+        /**
+         * The stack trace where the error originated.  May be null.
+         */
+        public String stackTrace;
+
+        /**
+         * to be deprecated: This value will always be null.
+         */
+        public byte[] crashData = null;
+
+        public ProcessErrorStateInfo() {
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(condition);
+            dest.writeString(processName);
+            dest.writeInt(pid);
+            dest.writeInt(uid);
+            dest.writeString(tag);
+            dest.writeString(shortMsg);
+            dest.writeString(longMsg);
+            dest.writeString(stackTrace);
+        }
+
+        public void readFromParcel(Parcel source) {
+            condition = source.readInt();
+            processName = source.readString();
+            pid = source.readInt();
+            uid = source.readInt();
+            tag = source.readString();
+            shortMsg = source.readString();
+            longMsg = source.readString();
+            stackTrace = source.readString();
+        }
+
+        public static final @android.annotation.NonNull Creator<ProcessErrorStateInfo> CREATOR =
+                new Creator<ProcessErrorStateInfo>() {
+            public ProcessErrorStateInfo createFromParcel(Parcel source) {
+                return new ProcessErrorStateInfo(source);
+            }
+            public ProcessErrorStateInfo[] newArray(int size) {
+                return new ProcessErrorStateInfo[size];
+            }
+        };
+
+        private ProcessErrorStateInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+
+    /**
+     * Returns a list of any processes that are currently in an error condition.  The result
+     * will be null if all processes are running properly at this time.
+     *
+     * @return Returns a list of ProcessErrorStateInfo records, or null if there are no
+     * current error conditions (it will not return an empty list).  This list ordering is not
+     * specified.
+     */
+    public List<ProcessErrorStateInfo> getProcessesInErrorState() {
+        try {
+            return getService().getProcessesInErrorState();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Information you can retrieve about a running process.
+     */
+    public static class RunningAppProcessInfo implements Parcelable {
+        /**
+         * The name of the process that this object is associated with
+         */
+        public String processName;
+
+        /**
+         * The pid of this process; 0 if none
+         */
+        public int pid;
+
+        /**
+         * The user id of this process.
+         */
+        public int uid;
+
+        /**
+         * All packages that have been loaded into the process.
+         */
+        public String pkgList[];
+
+        /**
+         * Constant for {@link #flags}: this is an app that is unable to
+         * correctly save its state when going to the background,
+         * so it can not be killed while in the background.
+         * @hide
+         */
+        public static final int FLAG_CANT_SAVE_STATE = 1<<0;
+
+        /**
+         * Constant for {@link #flags}: this process is associated with a
+         * persistent system app.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public static final int FLAG_PERSISTENT = 1<<1;
+
+        /**
+         * Constant for {@link #flags}: this process is associated with a
+         * persistent system app.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public static final int FLAG_HAS_ACTIVITIES = 1<<2;
+
+        /**
+         * Flags of information.  May be any of
+         * {@link #FLAG_CANT_SAVE_STATE}.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public int flags;
+
+        /**
+         * Last memory trim level reported to the process: corresponds to
+         * the values supplied to {@link android.content.ComponentCallbacks2#onTrimMemory(int)
+         * ComponentCallbacks2.onTrimMemory(int)}.
+         */
+        public int lastTrimLevel;
+
+        /** @hide */
+        @IntDef(prefix = { "IMPORTANCE_" }, value = {
+                IMPORTANCE_FOREGROUND,
+                IMPORTANCE_FOREGROUND_SERVICE,
+                IMPORTANCE_TOP_SLEEPING,
+                IMPORTANCE_VISIBLE,
+                IMPORTANCE_PERCEPTIBLE,
+                IMPORTANCE_CANT_SAVE_STATE,
+                IMPORTANCE_SERVICE,
+                IMPORTANCE_CACHED,
+                IMPORTANCE_GONE,
+        })
+        @Retention(RetentionPolicy.SOURCE)
+        public @interface Importance {}
+
+        /**
+         * Constant for {@link #importance}: This process is running the
+         * foreground UI; that is, it is the thing currently at the top of the screen
+         * that the user is interacting with.
+         */
+        public static final int IMPORTANCE_FOREGROUND = 100;
+
+        /**
+         * Constant for {@link #importance}: This process is running a foreground
+         * service, for example to perform music playback even while the user is
+         * not immediately in the app.  This generally indicates that the process
+         * is doing something the user actively cares about.
+         */
+        public static final int IMPORTANCE_FOREGROUND_SERVICE = 125;
+
+        /**
+         * @deprecated Pre-{@link android.os.Build.VERSION_CODES#P} version of
+         * {@link #IMPORTANCE_TOP_SLEEPING}.  As of Android
+         * {@link android.os.Build.VERSION_CODES#P}, this is considered much less
+         * important since we want to reduce what apps can do when the screen is off.
+         */
+        @Deprecated
+        public static final int IMPORTANCE_TOP_SLEEPING_PRE_28 = 150;
+
+        /**
+         * Constant for {@link #importance}: This process is running something
+         * that is actively visible to the user, though not in the immediate
+         * foreground.  This may be running a window that is behind the current
+         * foreground (so paused and with its state saved, not interacting with
+         * the user, but visible to them to some degree); it may also be running
+         * other services under the system's control that it inconsiders important.
+         */
+        public static final int IMPORTANCE_VISIBLE = 200;
+
+        /**
+         * Constant for {@link #importance}: {@link #IMPORTANCE_PERCEPTIBLE} had this wrong value
+         * before {@link Build.VERSION_CODES#O}.  Since the {@link Build.VERSION_CODES#O} SDK,
+         * the value of {@link #IMPORTANCE_PERCEPTIBLE} has been fixed.
+         *
+         * <p>The system will return this value instead of {@link #IMPORTANCE_PERCEPTIBLE}
+         * on Android versions below {@link Build.VERSION_CODES#O}.
+         *
+         * <p>On Android version {@link Build.VERSION_CODES#O} and later, this value will still be
+         * returned for apps with the target API level below {@link Build.VERSION_CODES#O}.
+         * For apps targeting version {@link Build.VERSION_CODES#O} and later,
+         * the correct value {@link #IMPORTANCE_PERCEPTIBLE} will be returned.
+         */
+        public static final int IMPORTANCE_PERCEPTIBLE_PRE_26 = 130;
+
+        /**
+         * Constant for {@link #importance}: This process is not something the user
+         * is directly aware of, but is otherwise perceptible to them to some degree.
+         */
+        public static final int IMPORTANCE_PERCEPTIBLE = 230;
+
+        /**
+         * Constant for {@link #importance}: {@link #IMPORTANCE_CANT_SAVE_STATE} had
+         * this wrong value
+         * before {@link Build.VERSION_CODES#O}.  Since the {@link Build.VERSION_CODES#O} SDK,
+         * the value of {@link #IMPORTANCE_CANT_SAVE_STATE} has been fixed.
+         *
+         * <p>The system will return this value instead of {@link #IMPORTANCE_CANT_SAVE_STATE}
+         * on Android versions below {@link Build.VERSION_CODES#O}.
+         *
+         * <p>On Android version {@link Build.VERSION_CODES#O} after, this value will still be
+         * returned for apps with the target API level below {@link Build.VERSION_CODES#O}.
+         * For apps targeting version {@link Build.VERSION_CODES#O} and later,
+         * the correct value {@link #IMPORTANCE_CANT_SAVE_STATE} will be returned.
+         *
+         * @hide
+         */
+        @TestApi
+        public static final int IMPORTANCE_CANT_SAVE_STATE_PRE_26 = 170;
+
+        /**
+         * Constant for {@link #importance}: This process is contains services
+         * that should remain running.  These are background services apps have
+         * started, not something the user is aware of, so they may be killed by
+         * the system relatively freely (though it is generally desired that they
+         * stay running as long as they want to).
+         */
+        public static final int IMPORTANCE_SERVICE = 300;
+
+        /**
+         * Constant for {@link #importance}: This process is running the foreground
+         * UI, but the device is asleep so it is not visible to the user.  Though the
+         * system will try hard to keep its process from being killed, in all other
+         * ways we consider it a kind of cached process, with the limitations that go
+         * along with that state: network access, running background services, etc.
+         */
+        public static final int IMPORTANCE_TOP_SLEEPING = 325;
+
+        /**
+         * Constant for {@link #importance}: This process is running an
+         * application that can not save its state, and thus can't be killed
+         * while in the background.  This will be used with apps that have
+         * {@link android.R.attr#cantSaveState} set on their application tag.
+         */
+        public static final int IMPORTANCE_CANT_SAVE_STATE = 350;
+
+        /**
+         * Constant for {@link #importance}: This process process contains
+         * cached code that is expendable, not actively running any app components
+         * we care about.
+         */
+        public static final int IMPORTANCE_CACHED = 400;
+
+        /**
+         * @deprecated Renamed to {@link #IMPORTANCE_CACHED}.
+         */
+        public static final int IMPORTANCE_BACKGROUND = IMPORTANCE_CACHED;
+
+        /**
+         * Constant for {@link #importance}: This process is empty of any
+         * actively running code.
+         * @deprecated This value is no longer reported, use {@link #IMPORTANCE_CACHED} instead.
+         */
+        @Deprecated
+        public static final int IMPORTANCE_EMPTY = 500;
+
+        /**
+         * Constant for {@link #importance}: This process does not exist.
+         */
+        public static final int IMPORTANCE_GONE = 1000;
+
+        /**
+         * Convert a proc state to the correspondent IMPORTANCE_* constant.  If the return value
+         * will be passed to a client, use {@link #procStateToImportanceForClient}.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public static @Importance int procStateToImportance(int procState) {
+            if (procState == PROCESS_STATE_NONEXISTENT) {
+                return IMPORTANCE_GONE;
+            } else if (procState >= PROCESS_STATE_HOME) {
+                return IMPORTANCE_CACHED;
+            } else if (procState == PROCESS_STATE_HEAVY_WEIGHT) {
+                return IMPORTANCE_CANT_SAVE_STATE;
+            } else if (procState >= PROCESS_STATE_TOP_SLEEPING) {
+                return IMPORTANCE_TOP_SLEEPING;
+            } else if (procState >= PROCESS_STATE_SERVICE) {
+                return IMPORTANCE_SERVICE;
+            } else if (procState >= PROCESS_STATE_TRANSIENT_BACKGROUND) {
+                return IMPORTANCE_PERCEPTIBLE;
+            } else if (procState >= PROCESS_STATE_IMPORTANT_FOREGROUND) {
+                return IMPORTANCE_VISIBLE;
+            } else if (procState >= PROCESS_STATE_FOREGROUND_SERVICE_LOCATION) {
+                return IMPORTANCE_FOREGROUND_SERVICE;
+            } else {
+                return IMPORTANCE_FOREGROUND;
+            }
+        }
+
+        /**
+         * Convert a proc state to the correspondent IMPORTANCE_* constant for a client represented
+         * by a given {@link Context}, with converting {@link #IMPORTANCE_PERCEPTIBLE}
+         * and {@link #IMPORTANCE_CANT_SAVE_STATE} to the corresponding "wrong" value if the
+         * client's target SDK < {@link VERSION_CODES#O}.
+         * @hide
+         */
+        public static @Importance int procStateToImportanceForClient(int procState,
+                Context clientContext) {
+            return procStateToImportanceForTargetSdk(procState,
+                    clientContext.getApplicationInfo().targetSdkVersion);
+        }
+
+        /**
+         * See {@link #procStateToImportanceForClient}.
+         * @hide
+         */
+        public static @Importance int procStateToImportanceForTargetSdk(int procState,
+                int targetSdkVersion) {
+            final int importance = procStateToImportance(procState);
+
+            // For pre O apps, convert to the old, wrong values.
+            if (targetSdkVersion < VERSION_CODES.O) {
+                switch (importance) {
+                    case IMPORTANCE_PERCEPTIBLE:
+                        return IMPORTANCE_PERCEPTIBLE_PRE_26;
+                    case IMPORTANCE_TOP_SLEEPING:
+                        return IMPORTANCE_TOP_SLEEPING_PRE_28;
+                    case IMPORTANCE_CANT_SAVE_STATE:
+                        return IMPORTANCE_CANT_SAVE_STATE_PRE_26;
+                }
+            }
+            return importance;
+        }
+
+        /** @hide */
+        public static int importanceToProcState(@Importance int importance) {
+            if (importance == IMPORTANCE_GONE) {
+                return PROCESS_STATE_NONEXISTENT;
+            } else if (importance >= IMPORTANCE_CACHED) {
+                return PROCESS_STATE_HOME;
+            } else if (importance >= IMPORTANCE_CANT_SAVE_STATE) {
+                return PROCESS_STATE_HEAVY_WEIGHT;
+            } else if (importance >= IMPORTANCE_TOP_SLEEPING) {
+                return PROCESS_STATE_TOP_SLEEPING;
+            } else if (importance >= IMPORTANCE_SERVICE) {
+                return PROCESS_STATE_SERVICE;
+            } else if (importance >= IMPORTANCE_PERCEPTIBLE) {
+                return PROCESS_STATE_TRANSIENT_BACKGROUND;
+            } else if (importance >= IMPORTANCE_VISIBLE) {
+                return PROCESS_STATE_IMPORTANT_FOREGROUND;
+            } else if (importance >= IMPORTANCE_TOP_SLEEPING_PRE_28) {
+                return PROCESS_STATE_IMPORTANT_FOREGROUND;
+            } else if (importance >= IMPORTANCE_FOREGROUND_SERVICE) {
+                return PROCESS_STATE_FOREGROUND_SERVICE;
+                // TODO: Asymmetrical mapping for LOCATION service type. Ok?
+            } else {
+                return PROCESS_STATE_TOP;
+            }
+        }
+
+        /**
+         * The relative importance level that the system places on this process.
+         * These constants are numbered so that "more important" values are
+         * always smaller than "less important" values.
+         */
+        public @Importance int importance;
+
+        /**
+         * An additional ordering within a particular {@link #importance}
+         * category, providing finer-grained information about the relative
+         * utility of processes within a category.  This number means nothing
+         * except that a smaller values are more recently used (and thus
+         * more important).  Currently an LRU value is only maintained for
+         * the {@link #IMPORTANCE_CACHED} category, though others may
+         * be maintained in the future.
+         */
+        public int lru;
+
+        /**
+         * Constant for {@link #importanceReasonCode}: nothing special has
+         * been specified for the reason for this level.
+         */
+        public static final int REASON_UNKNOWN = 0;
+
+        /**
+         * Constant for {@link #importanceReasonCode}: one of the application's
+         * content providers is being used by another process.  The pid of
+         * the client process is in {@link #importanceReasonPid} and the
+         * target provider in this process is in
+         * {@link #importanceReasonComponent}.
+         */
+        public static final int REASON_PROVIDER_IN_USE = 1;
+
+        /**
+         * Constant for {@link #importanceReasonCode}: one of the application's
+         * content providers is being used by another process.  The pid of
+         * the client process is in {@link #importanceReasonPid} and the
+         * target provider in this process is in
+         * {@link #importanceReasonComponent}.
+         */
+        public static final int REASON_SERVICE_IN_USE = 2;
+
+        /**
+         * The reason for {@link #importance}, if any.
+         */
+        public int importanceReasonCode;
+
+        /**
+         * For the specified values of {@link #importanceReasonCode}, this
+         * is the process ID of the other process that is a client of this
+         * process.  This will be 0 if no other process is using this one.
+         */
+        public int importanceReasonPid;
+
+        /**
+         * For the specified values of {@link #importanceReasonCode}, this
+         * is the name of the component that is being used in this process.
+         */
+        public ComponentName importanceReasonComponent;
+
+        /**
+         * When {@link #importanceReasonPid} is non-0, this is the importance
+         * of the other pid. @hide
+         */
+        public int importanceReasonImportance;
+
+        /**
+         * Current process state, as per PROCESS_STATE_* constants.
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public int processState;
+
+        /**
+         * Whether the app is focused in multi-window environment.
+         * @hide
+         */
+        public boolean isFocused;
+
+        /**
+         * Copy of {@link com.android.server.am.ProcessRecord#lastActivityTime} of the process.
+         * @hide
+         */
+        public long lastActivityTime;
+
+        public RunningAppProcessInfo() {
+            importance = IMPORTANCE_FOREGROUND;
+            importanceReasonCode = REASON_UNKNOWN;
+            processState = PROCESS_STATE_IMPORTANT_FOREGROUND;
+            isFocused = false;
+            lastActivityTime = 0;
+        }
+
+        public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) {
+            processName = pProcessName;
+            pid = pPid;
+            pkgList = pArr;
+            isFocused = false;
+            lastActivityTime = 0;
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(processName);
+            dest.writeInt(pid);
+            dest.writeInt(uid);
+            dest.writeStringArray(pkgList);
+            dest.writeInt(this.flags);
+            dest.writeInt(lastTrimLevel);
+            dest.writeInt(importance);
+            dest.writeInt(lru);
+            dest.writeInt(importanceReasonCode);
+            dest.writeInt(importanceReasonPid);
+            ComponentName.writeToParcel(importanceReasonComponent, dest);
+            dest.writeInt(importanceReasonImportance);
+            dest.writeInt(processState);
+            dest.writeInt(isFocused ? 1 : 0);
+            dest.writeLong(lastActivityTime);
+        }
+
+        public void readFromParcel(Parcel source) {
+            processName = source.readString();
+            pid = source.readInt();
+            uid = source.readInt();
+            pkgList = source.readStringArray();
+            flags = source.readInt();
+            lastTrimLevel = source.readInt();
+            importance = source.readInt();
+            lru = source.readInt();
+            importanceReasonCode = source.readInt();
+            importanceReasonPid = source.readInt();
+            importanceReasonComponent = ComponentName.readFromParcel(source);
+            importanceReasonImportance = source.readInt();
+            processState = source.readInt();
+            isFocused = source.readInt() != 0;
+            lastActivityTime = source.readLong();
+        }
+
+        public static final @android.annotation.NonNull Creator<RunningAppProcessInfo> CREATOR =
+            new Creator<RunningAppProcessInfo>() {
+            public RunningAppProcessInfo createFromParcel(Parcel source) {
+                return new RunningAppProcessInfo(source);
+            }
+            public RunningAppProcessInfo[] newArray(int size) {
+                return new RunningAppProcessInfo[size];
+            }
+        };
+
+        private RunningAppProcessInfo(Parcel source) {
+            readFromParcel(source);
+        }
+    }
+
+    /**
+     * Returns a list of application processes installed on external media
+     * that are running on the device.
+     *
+     * <p><b>Note: this method is only intended for debugging or building
+     * a user-facing process management UI.</b></p>
+     *
+     * @return Returns a list of ApplicationInfo records, or null if none
+     * This list ordering is not specified.
+     * @hide
+     */
+    public List<ApplicationInfo> getRunningExternalApplications() {
+        try {
+            return getService().getRunningExternalApplications();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Query whether the user has enabled background restrictions for this app.
+     *
+     * <p> The user may chose to do this, if they see that an app is consuming an unreasonable
+     * amount of battery while in the background. </p>
+     *
+     * <p> If true, any work that the app tries to do will be aggressively restricted while it is in
+     * the background. At a minimum, jobs and alarms will not execute and foreground services
+     * cannot be started unless an app activity is in the foreground. </p>
+     *
+     * <p><b> Note that these restrictions stay in effect even when the device is charging.</b></p>
+     *
+     * @return true if user has enforced background restrictions for this app, false otherwise.
+     */
+    public boolean isBackgroundRestricted() {
+        try {
+            return getService().isBackgroundRestricted(mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the memory trim mode for a process and schedules a memory trim operation.
+     *
+     * <p><b>Note: this method is only intended for testing framework.</b></p>
+     *
+     * @return Returns true if successful.
+     * @hide
+     */
+    public boolean setProcessMemoryTrimLevel(String process, int userId, int level) {
+        try {
+            return getService().setProcessMemoryTrimLevel(process, userId,
+                    level);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns a list of application processes that are running on the device.
+     *
+     * <p><b>Note: this method is only intended for debugging or building
+     * a user-facing process management UI.</b></p>
+     *
+     * @return Returns a list of RunningAppProcessInfo records, or null if there are no
+     * running processes (it will not return an empty list).  This list ordering is not
+     * specified.
+     */
+    public List<RunningAppProcessInfo> getRunningAppProcesses() {
+        try {
+            return getService().getRunningAppProcesses();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the importance of a given package name, based on the processes that are
+     * currently running.  The return value is one of the importance constants defined
+     * in {@link RunningAppProcessInfo}, giving you the highest importance of all the
+     * processes that this package has code running inside of.  If there are no processes
+     * running its code, {@link RunningAppProcessInfo#IMPORTANCE_GONE} is returned.
+     * @hide
+     */
+    @SystemApi @TestApi
+    @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+    public @RunningAppProcessInfo.Importance int getPackageImportance(String packageName) {
+        try {
+            int procState = getService().getPackageProcessState(packageName,
+                    mContext.getOpPackageName());
+            return RunningAppProcessInfo.procStateToImportanceForClient(procState, mContext);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the importance of a given uid, based on the processes that are
+     * currently running.  The return value is one of the importance constants defined
+     * in {@link RunningAppProcessInfo}, giving you the highest importance of all the
+     * processes that this uid has running.  If there are no processes
+     * running its code, {@link RunningAppProcessInfo#IMPORTANCE_GONE} is returned.
+     * @hide
+     */
+    @SystemApi @TestApi
+    @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+    public @RunningAppProcessInfo.Importance int getUidImportance(int uid) {
+        try {
+            int procState = getService().getUidProcessState(uid,
+                    mContext.getOpPackageName());
+            return RunningAppProcessInfo.procStateToImportanceForClient(procState, mContext);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Callback to get reports about changes to the importance of a uid.  Use with
+     * {@link #addOnUidImportanceListener}.
+     * @hide
+     */
+    @SystemApi @TestApi
+    public interface OnUidImportanceListener {
+        /**
+         * The importance if a given uid has changed.  Will be one of the importance
+         * values in {@link RunningAppProcessInfo};
+         * {@link RunningAppProcessInfo#IMPORTANCE_GONE IMPORTANCE_GONE} will be reported
+         * when the uid is no longer running at all.  This callback will happen on a thread
+         * from a thread pool, not the main UI thread.
+         * @param uid The uid whose importance has changed.
+         * @param importance The new importance value as per {@link RunningAppProcessInfo}.
+         */
+        void onUidImportance(int uid, @RunningAppProcessInfo.Importance int importance);
+    }
+
+    /**
+     * Start monitoring changes to the imoportance of uids running in the system.
+     * @param listener The listener callback that will receive change reports.
+     * @param importanceCutpoint The level of importance in which the caller is interested
+     * in differences.  For example, if {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}
+     * is used here, you will receive a call each time a uids importance transitions between
+     * being <= {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE} and
+     * > {@link RunningAppProcessInfo#IMPORTANCE_PERCEPTIBLE}.
+     *
+     * <p>The caller must hold the {@link android.Manifest.permission#PACKAGE_USAGE_STATS}
+     * permission to use this feature.</p>
+     *
+     * @throws IllegalArgumentException If the listener is already registered.
+     * @throws SecurityException If the caller does not hold
+     * {@link android.Manifest.permission#PACKAGE_USAGE_STATS}.
+     * @hide
+     */
+    @SystemApi @TestApi
+    @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+    public void addOnUidImportanceListener(OnUidImportanceListener listener,
+            @RunningAppProcessInfo.Importance int importanceCutpoint) {
+        synchronized (this) {
+            if (mImportanceListeners.containsKey(listener)) {
+                throw new IllegalArgumentException("Listener already registered: " + listener);
+            }
+            // TODO: implement the cut point in the system process to avoid IPCs.
+            UidObserver observer = new UidObserver(listener, mContext);
+            try {
+                getService().registerUidObserver(observer,
+                        UID_OBSERVER_PROCSTATE | UID_OBSERVER_GONE,
+                        RunningAppProcessInfo.importanceToProcState(importanceCutpoint),
+                        mContext.getOpPackageName());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mImportanceListeners.put(listener, observer);
+        }
+    }
+
+    /**
+     * Remove an importance listener that was previously registered with
+     * {@link #addOnUidImportanceListener}.
+     *
+     * @throws IllegalArgumentException If the listener is not registered.
+     * @hide
+     */
+    @SystemApi @TestApi
+    @RequiresPermission(Manifest.permission.PACKAGE_USAGE_STATS)
+    public void removeOnUidImportanceListener(OnUidImportanceListener listener) {
+        synchronized (this) {
+            UidObserver observer = mImportanceListeners.remove(listener);
+            if (observer == null) {
+                throw new IllegalArgumentException("Listener not registered: " + listener);
+            }
+            try {
+                getService().unregisterUidObserver(observer);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Return global memory state information for the calling process.  This
+     * does not fill in all fields of the {@link RunningAppProcessInfo}.  The
+     * only fields that will be filled in are
+     * {@link RunningAppProcessInfo#pid},
+     * {@link RunningAppProcessInfo#uid},
+     * {@link RunningAppProcessInfo#lastTrimLevel},
+     * {@link RunningAppProcessInfo#importance},
+     * {@link RunningAppProcessInfo#lru}, and
+     * {@link RunningAppProcessInfo#importanceReasonCode}.
+     */
+    static public void getMyMemoryState(RunningAppProcessInfo outState) {
+        try {
+            getService().getMyMemoryState(outState);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return information about the memory usage of one or more processes.
+     *
+     * <p><b>Note: this method is only intended for debugging or building
+     * a user-facing process management UI.</b></p>
+     *
+     * <p>As of {@link android.os.Build.VERSION_CODES#Q Android Q}, for regular apps this method
+     * will only return information about the memory info for the processes running as the
+     * caller's uid; no other process memory info is available and will be zero.
+     * Also of {@link android.os.Build.VERSION_CODES#Q Android Q} the sample rate allowed
+     * by this API is significantly limited, if called faster the limit you will receive the
+     * same data as the previous call.</p>
+     *
+     * @param pids The pids of the processes whose memory usage is to be
+     * retrieved.
+     * @return Returns an array of memory information, one for each
+     * requested pid.
+     */
+    public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) {
+        try {
+            return getService().getProcessMemoryInfo(pids);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @deprecated This is now just a wrapper for
+     * {@link #killBackgroundProcesses(String)}; the previous behavior here
+     * is no longer available to applications because it allows them to
+     * break other applications by removing their alarms, stopping their
+     * services, etc.
+     */
+    @Deprecated
+    public void restartPackage(String packageName) {
+        killBackgroundProcesses(packageName);
+    }
+
+    /**
+     * Have the system immediately kill all background processes associated
+     * with the given package.  This is the same as the kernel killing those
+     * processes to reclaim memory; the system will take care of restarting
+     * these processes in the future as needed.
+     *
+     * @param packageName The name of the package whose processes are to
+     * be killed.
+     */
+    @RequiresPermission(Manifest.permission.KILL_BACKGROUND_PROCESSES)
+    public void killBackgroundProcesses(String packageName) {
+        try {
+            getService().killBackgroundProcesses(packageName,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Kills the specified UID.
+     * @param uid The UID to kill.
+     * @param reason The reason for the kill.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.KILL_UID)
+    public void killUid(int uid, String reason) {
+        try {
+            getService().killUid(UserHandle.getAppId(uid),
+                    UserHandle.getUserId(uid), reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Have the system perform a force stop of everything associated with
+     * the given application package.  All processes that share its uid
+     * will be killed, all services it has running stopped, all activities
+     * removed, etc.  In addition, a {@link Intent#ACTION_PACKAGE_RESTARTED}
+     * broadcast will be sent, so that any of its registered alarms can
+     * be stopped, notifications removed, etc.
+     *
+     * <p>You must hold the permission
+     * {@link android.Manifest.permission#FORCE_STOP_PACKAGES} to be able to
+     * call this method.
+     *
+     * @param packageName The name of the package to be stopped.
+     * @param userId The user for which the running package is to be stopped.
+     *
+     * @hide This is not available to third party applications due to
+     * it allowing them to break other applications by stopping their
+     * services, removing their alarms, etc.
+     */
+    @UnsupportedAppUsage
+    public void forceStopPackageAsUser(String packageName, int userId) {
+        try {
+            getService().forceStopPackage(packageName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @see #forceStopPackageAsUser(String, int)
+     * @hide
+     */
+    @SystemApi @TestApi
+    @RequiresPermission(Manifest.permission.FORCE_STOP_PACKAGES)
+    public void forceStopPackage(String packageName) {
+        forceStopPackageAsUser(packageName, mContext.getUserId());
+    }
+
+    /**
+     * Sets the current locales of the device. Calling app must have the permission
+     * {@code android.permission.CHANGE_CONFIGURATION} and
+     * {@code android.permission.WRITE_SETTINGS}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public void setDeviceLocales(@NonNull LocaleList locales) {
+        LocalePicker.updateLocales(locales);
+    }
+
+    /**
+     * Returns a list of supported locales by this system. It includes all locales that are
+     * selectable by the user, potentially including locales that the framework does not have
+     * translated resources for. To get locales that the framework has translated resources for, use
+     * {@code Resources.getSystem().getAssets().getLocales()} instead.
+     *
+     * @hide
+     */
+    @SystemApi
+    public @NonNull Collection<Locale> getSupportedLocales() {
+        ArrayList<Locale> locales = new ArrayList<>();
+        for (String localeTag : LocalePicker.getSupportedLocales(mContext)) {
+            locales.add(Locale.forLanguageTag(localeTag));
+        }
+        return locales;
+    }
+
+    /**
+     * Get the device configuration attributes.
+     */
+    public ConfigurationInfo getDeviceConfigurationInfo() {
+        try {
+            return getTaskService().getDeviceConfigurationInfo();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Get the preferred density of icons for the launcher. This is used when
+     * custom drawables are created (e.g., for shortcuts).
+     *
+     * @return density in terms of DPI
+     */
+    public int getLauncherLargeIconDensity() {
+        final Resources res = mContext.getResources();
+        final int density = res.getDisplayMetrics().densityDpi;
+        final int sw = res.getConfiguration().smallestScreenWidthDp;
+
+        if (sw < 600) {
+            // Smaller than approx 7" tablets, use the regular icon size.
+            return density;
+        }
+
+        switch (density) {
+            case DisplayMetrics.DENSITY_LOW:
+                return DisplayMetrics.DENSITY_MEDIUM;
+            case DisplayMetrics.DENSITY_MEDIUM:
+                return DisplayMetrics.DENSITY_HIGH;
+            case DisplayMetrics.DENSITY_TV:
+                return DisplayMetrics.DENSITY_XHIGH;
+            case DisplayMetrics.DENSITY_HIGH:
+                return DisplayMetrics.DENSITY_XHIGH;
+            case DisplayMetrics.DENSITY_XHIGH:
+                return DisplayMetrics.DENSITY_XXHIGH;
+            case DisplayMetrics.DENSITY_XXHIGH:
+                return DisplayMetrics.DENSITY_XHIGH * 2;
+            default:
+                // The density is some abnormal value.  Return some other
+                // abnormal value that is a reasonable scaling of it.
+                return (int)((density*1.5f)+.5f);
+        }
+    }
+
+    /**
+     * Get the preferred launcher icon size. This is used when custom drawables
+     * are created (e.g., for shortcuts).
+     *
+     * @return dimensions of square icons in terms of pixels
+     */
+    public int getLauncherLargeIconSize() {
+        return getLauncherLargeIconSizeInner(mContext);
+    }
+
+    static int getLauncherLargeIconSizeInner(Context context) {
+        final Resources res = context.getResources();
+        final int size = res.getDimensionPixelSize(android.R.dimen.app_icon_size);
+        final int sw = res.getConfiguration().smallestScreenWidthDp;
+
+        if (sw < 600) {
+            // Smaller than approx 7" tablets, use the regular icon size.
+            return size;
+        }
+
+        final int density = res.getDisplayMetrics().densityDpi;
+
+        switch (density) {
+            case DisplayMetrics.DENSITY_LOW:
+                return (size * DisplayMetrics.DENSITY_MEDIUM) / DisplayMetrics.DENSITY_LOW;
+            case DisplayMetrics.DENSITY_MEDIUM:
+                return (size * DisplayMetrics.DENSITY_HIGH) / DisplayMetrics.DENSITY_MEDIUM;
+            case DisplayMetrics.DENSITY_TV:
+                return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH;
+            case DisplayMetrics.DENSITY_HIGH:
+                return (size * DisplayMetrics.DENSITY_XHIGH) / DisplayMetrics.DENSITY_HIGH;
+            case DisplayMetrics.DENSITY_XHIGH:
+                return (size * DisplayMetrics.DENSITY_XXHIGH) / DisplayMetrics.DENSITY_XHIGH;
+            case DisplayMetrics.DENSITY_XXHIGH:
+                return (size * DisplayMetrics.DENSITY_XHIGH*2) / DisplayMetrics.DENSITY_XXHIGH;
+            default:
+                // The density is some abnormal value.  Return some other
+                // abnormal value that is a reasonable scaling of it.
+                return (int)((size*1.5f) + .5f);
+        }
+    }
+
+    /**
+     * Returns "true" if the user interface is currently being messed with
+     * by a monkey.
+     */
+    public static boolean isUserAMonkey() {
+        try {
+            return getService().isUserAMonkey();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns "true" if device is running in a test harness.
+     *
+     * @deprecated this method is false for all user builds. Users looking to check if their device
+     * is running in a device farm should see {@link #isRunningInUserTestHarness()}.
+     */
+    @Deprecated
+    public static boolean isRunningInTestHarness() {
+        return SystemProperties.getBoolean("ro.test_harness", false);
+    }
+
+    /**
+     * Returns "true" if the device is running in Test Harness Mode.
+     *
+     * <p>Test Harness Mode is a feature that allows devices to run without human interaction in a
+     * device farm/testing harness (such as Firebase Test Lab). You should check this method if you
+     * want your app to behave differently when running in a test harness to skip setup screens that
+     * would impede UI testing. e.g. a keyboard application that has a full screen setup page for
+     * the first time it is launched.
+     *
+     * <p>Note that you should <em>not</em> use this to determine whether or not your app is running
+     * an instrumentation test, as it is not set for a standard device running a test.
+     */
+    public static boolean isRunningInUserTestHarness() {
+        return SystemProperties.getBoolean("persist.sys.test_harness", false);
+    }
+
+    /**
+     * Unsupported compiled sdk warning should always be shown for the intput activity
+     * even in cases where the system would normally not show the warning. E.g. when running in a
+     * test harness.
+     *
+     * @param activity The component name of the activity to always show the warning for.
+     *
+     * @hide
+     */
+    @TestApi
+    public void alwaysShowUnsupportedCompileSdkWarning(ComponentName activity) {
+        try {
+            getTaskService().alwaysShowUnsupportedCompileSdkWarning(activity);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the launch count of each installed package.
+     *
+     * @hide
+     */
+    /*public Map<String, Integer> getAllPackageLaunchCounts() {
+        try {
+            IUsageStats usageStatsService = IUsageStats.Stub.asInterface(
+                    ServiceManager.getService("usagestats"));
+            if (usageStatsService == null) {
+                return new HashMap<String, Integer>();
+            }
+
+            UsageStats.PackageStats[] allPkgUsageStats = usageStatsService.getAllPkgUsageStats(
+                    ActivityThread.currentPackageName());
+            if (allPkgUsageStats == null) {
+                return new HashMap<String, Integer>();
+            }
+
+            Map<String, Integer> launchCounts = new HashMap<String, Integer>();
+            for (UsageStats.PackageStats pkgUsageStats : allPkgUsageStats) {
+                launchCounts.put(pkgUsageStats.getPackageName(), pkgUsageStats.getLaunchCount());
+            }
+
+            return launchCounts;
+        } catch (RemoteException e) {
+            Log.w(TAG, "Could not query launch counts", e);
+            return new HashMap<String, Integer>();
+        }
+    }*/
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static int checkComponentPermission(String permission, int uid,
+            int owningUid, boolean exported) {
+        // Root, system server get to do everything.
+        final int appId = UserHandle.getAppId(uid);
+        if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+        // Isolated processes don't get any permissions.
+        if (UserHandle.isIsolated(uid)) {
+            return PackageManager.PERMISSION_DENIED;
+        }
+        // If there is a uid that owns whatever is being accessed, it has
+        // blanket access to it regardless of the permissions it requires.
+        if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+        // If the target is not exported, then nobody else can get to it.
+        if (!exported) {
+            /*
+            RuntimeException here = new RuntimeException("here");
+            here.fillInStackTrace();
+            Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
+                    here);
+            */
+            return PackageManager.PERMISSION_DENIED;
+        }
+        if (permission == null) {
+            return PackageManager.PERMISSION_GRANTED;
+        }
+        try {
+            return AppGlobals.getPackageManager()
+                    .checkUidPermission(permission, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public static int checkUidPermission(String permission, int uid) {
+        try {
+            return AppGlobals.getPackageManager()
+                    .checkUidPermission(permission, uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     * Helper for dealing with incoming user arguments to system service calls.
+     * Takes care of checking permissions and converting USER_CURRENT to the
+     * actual current user.
+     *
+     * @param callingPid The pid of the incoming call, as per Binder.getCallingPid().
+     * @param callingUid The uid of the incoming call, as per Binder.getCallingUid().
+     * @param userId The user id argument supplied by the caller -- this is the user
+     * they want to run as.
+     * @param allowAll If true, we will allow USER_ALL.  This means you must be prepared
+     * to get a USER_ALL returned and deal with it correctly.  If false,
+     * an exception will be thrown if USER_ALL is supplied.
+     * @param requireFull If true, the caller must hold
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} to be able to run as a
+     * different user than their current process; otherwise they must hold
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS}.
+     * @param name Optional textual name of the incoming call; only for generating error messages.
+     * @param callerPackage Optional package name of caller; only for error messages.
+     *
+     * @return Returns the user ID that the call should run as.  Will always be a concrete
+     * user number, unless <var>allowAll</var> is true in which case it could also be
+     * USER_ALL.
+     */
+    public static int handleIncomingUser(int callingPid, int callingUid, int userId,
+            boolean allowAll, boolean requireFull, String name, String callerPackage) {
+        if (UserHandle.getUserId(callingUid) == userId) {
+            return userId;
+        }
+        try {
+            return getService().handleIncomingUser(callingPid,
+                    callingUid, userId, allowAll, requireFull, name, callerPackage);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the userId of the current foreground user. Requires system permissions.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(anyOf = {
+            "android.permission.INTERACT_ACROSS_USERS",
+            "android.permission.INTERACT_ACROSS_USERS_FULL"
+    })
+    public static int getCurrentUser() {
+        UserInfo ui;
+        try {
+            ui = getService().getCurrentUser();
+            return ui != null ? ui.id : 0;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @param userid the user's id. Zero indicates the default user.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean switchUser(int userid) {
+        try {
+            return getService().switchUser(userid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns whether switching to provided user was successful.
+     *
+     * @param user the user to switch to.
+     *
+     * @throws IllegalArgumentException if the user is null.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_USERS)
+    public boolean switchUser(@NonNull UserHandle user) {
+        if (user == null) {
+            throw new IllegalArgumentException("UserHandle cannot be null.");
+        }
+        return switchUser(user.getIdentifier());
+    }
+
+    /**
+     * Logs out current current foreground user by switching to the system user and stopping the
+     * user being switched from.
+     * @hide
+     */
+    public static void logoutCurrentUser() {
+        int currentUser = ActivityManager.getCurrentUser();
+        if (currentUser != UserHandle.USER_SYSTEM) {
+            try {
+                getService().switchUser(UserHandle.USER_SYSTEM);
+                getService().stopUser(currentUser, /* force= */ false, null);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /** {@hide} */
+    public static final int FLAG_OR_STOPPED = 1 << 0;
+    /** {@hide} */
+    public static final int FLAG_AND_LOCKED = 1 << 1;
+    /** {@hide} */
+    public static final int FLAG_AND_UNLOCKED = 1 << 2;
+    /** {@hide} */
+    public static final int FLAG_AND_UNLOCKING_OR_UNLOCKED = 1 << 3;
+
+    /**
+     * Return whether the given user is actively running.  This means that
+     * the user is in the "started" state, not "stopped" -- it is currently
+     * allowed to run code through scheduled alarms, receiving broadcasts,
+     * etc.  A started user may be either the current foreground user or a
+     * background user; the result here does not distinguish between the two.
+     * @param userId the user's id. Zero indicates the default user.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public boolean isUserRunning(int userId) {
+        try {
+            return getService().isUserRunning(userId, 0);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    public boolean isVrModePackageEnabled(ComponentName component) {
+        try {
+            return getService().isVrModePackageEnabled(component);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Perform a system dump of various state associated with the given application
+     * package name.  This call blocks while the dump is being performed, so should
+     * not be done on a UI thread.  The data will be written to the given file
+     * descriptor as text.
+     * @param fd The file descriptor that the dump should be written to.  The file
+     * descriptor is <em>not</em> closed by this function; the caller continues to
+     * own it.
+     * @param packageName The name of the package that is to be dumped.
+     */
+    @RequiresPermission(Manifest.permission.DUMP)
+    public void dumpPackageState(FileDescriptor fd, String packageName) {
+        dumpPackageStateStatic(fd, packageName);
+    }
+
+    /**
+     * @hide
+     */
+    public static void dumpPackageStateStatic(FileDescriptor fd, String packageName) {
+        FileOutputStream fout = new FileOutputStream(fd);
+        PrintWriter pw = new FastPrintWriter(fout);
+        dumpService(pw, fd, "package", new String[] { packageName });
+        pw.println();
+        dumpService(pw, fd, Context.ACTIVITY_SERVICE, new String[] {
+                "-a", "package", packageName });
+        pw.println();
+        dumpService(pw, fd, "meminfo", new String[] { "--local", "--package", packageName });
+        pw.println();
+        dumpService(pw, fd, ProcessStats.SERVICE_NAME, new String[] { packageName });
+        pw.println();
+        dumpService(pw, fd, "usagestats", new String[] { packageName });
+        pw.println();
+        dumpService(pw, fd, BatteryStats.SERVICE_NAME, new String[] { packageName });
+        pw.flush();
+    }
+
+    /**
+     * @hide
+     */
+    public static boolean isSystemReady() {
+        if (!sSystemReady) {
+            if (ActivityThread.isSystem()) {
+                sSystemReady =
+                        LocalServices.getService(ActivityManagerInternal.class).isSystemReady();
+            } else {
+                // Since this is being called from outside system server, system should be
+                // ready by now.
+                sSystemReady = true;
+            }
+        }
+        return sSystemReady;
+    }
+
+    /**
+     * @hide
+     */
+    public static void broadcastStickyIntent(Intent intent, int userId) {
+        broadcastStickyIntent(intent, AppOpsManager.OP_NONE, userId);
+    }
+
+    /**
+     * Convenience for sending a sticky broadcast.  For internal use only.
+     *
+     * @hide
+     */
+    public static void broadcastStickyIntent(Intent intent, int appOp, int userId) {
+        try {
+            getService().broadcastIntent(
+                    null, intent, null, null, Activity.RESULT_OK, null, null,
+                    null /*permission*/, appOp, null, false, true, userId);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    public static void resumeAppSwitches() throws RemoteException {
+        getService().resumeAppSwitches();
+    }
+
+    /**
+     * @hide
+     */
+    public static void noteWakeupAlarm(PendingIntent ps, WorkSource workSource, int sourceUid,
+            String sourcePkg, String tag) {
+        try {
+            getService().noteWakeupAlarm((ps != null) ? ps.getTarget() : null, workSource,
+                    sourceUid, sourcePkg, tag);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public static void noteAlarmStart(PendingIntent ps, WorkSource workSource, int sourceUid,
+            String tag) {
+        try {
+            getService().noteAlarmStart((ps != null) ? ps.getTarget() : null, workSource,
+                    sourceUid, tag);
+        } catch (RemoteException ex) {
+        }
+    }
+
+
+    /**
+     * @hide
+     */
+    public static void noteAlarmFinish(PendingIntent ps, WorkSource workSource, int sourceUid,
+            String tag) {
+        try {
+            getService().noteAlarmFinish((ps != null) ? ps.getTarget() : null, workSource,
+                    sourceUid, tag);
+        } catch (RemoteException ex) {
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static IActivityManager getService() {
+        return IActivityManagerSingleton.get();
+    }
+
+    private static IActivityTaskManager getTaskService() {
+        return ActivityTaskManager.getService();
+    }
+
+    @UnsupportedAppUsage
+    private static final Singleton<IActivityManager> IActivityManagerSingleton =
+            new Singleton<IActivityManager>() {
+                @Override
+                protected IActivityManager create() {
+                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
+                    final IActivityManager am = IActivityManager.Stub.asInterface(b);
+                    return am;
+                }
+            };
+
+    private static void dumpService(PrintWriter pw, FileDescriptor fd, String name, String[] args) {
+        pw.print("DUMP OF SERVICE "); pw.print(name); pw.println(":");
+        IBinder service = ServiceManager.checkService(name);
+        if (service == null) {
+            pw.println("  (Service not found)");
+            pw.flush();
+            return;
+        }
+        pw.flush();
+        if (service instanceof Binder) {
+            // If this is a local object, it doesn't make sense to do an async dump with it,
+            // just directly dump.
+            try {
+                service.dump(fd, args);
+            } catch (Throwable e) {
+                pw.println("Failure dumping service:");
+                e.printStackTrace(pw);
+                pw.flush();
+            }
+        } else {
+            // Otherwise, it is remote, do the dump asynchronously to avoid blocking.
+            TransferPipe tp = null;
+            try {
+                pw.flush();
+                tp = new TransferPipe();
+                tp.setBufferPrefix("  ");
+                service.dumpAsync(tp.getWriteFd().getFileDescriptor(), args);
+                tp.go(fd, 10000);
+            } catch (Throwable e) {
+                if (tp != null) {
+                    tp.kill();
+                }
+                pw.println("Failure dumping service:");
+                e.printStackTrace(pw);
+            }
+        }
+    }
+
+    /**
+     * Request that the system start watching for the calling process to exceed a pss
+     * size as given here.  Once called, the system will look for any occasions where it
+     * sees the associated process with a larger pss size and, when this happens, automatically
+     * pull a heap dump from it and allow the user to share the data.  Note that this request
+     * continues running even if the process is killed and restarted.  To remove the watch,
+     * use {@link #clearWatchHeapLimit()}.
+     *
+     * <p>This API only works if the calling process has been marked as
+     * {@link ApplicationInfo#FLAG_DEBUGGABLE} or this is running on a debuggable
+     * (userdebug or eng) build.</p>
+     *
+     * <p>Callers can optionally implement {@link #ACTION_REPORT_HEAP_LIMIT} to directly
+     * handle heap limit reports themselves.</p>
+     *
+     * @param pssSize The size in bytes to set the limit at.
+     */
+    public void setWatchHeapLimit(long pssSize) {
+        try {
+            getService().setDumpHeapDebugLimit(null, 0, pssSize,
+                    mContext.getPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Action an app can implement to handle reports from {@link #setWatchHeapLimit(long)}.
+     * If your package has an activity handling this action, it will be launched with the
+     * heap data provided to it the same way as {@link Intent#ACTION_SEND}.  Note that to
+     * match the activty must support this action and a MIME type of "*&#47;*".
+     */
+    public static final String ACTION_REPORT_HEAP_LIMIT = "android.app.action.REPORT_HEAP_LIMIT";
+
+    /**
+     * Clear a heap watch limit previously set by {@link #setWatchHeapLimit(long)}.
+     */
+    public void clearWatchHeapLimit() {
+        try {
+            getService().setDumpHeapDebugLimit(null, 0, 0, null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return whether currently in lock task mode.  When in this mode
+     * no new tasks can be created or switched to.
+     *
+     * @see Activity#startLockTask()
+     *
+     * @deprecated Use {@link #getLockTaskModeState} instead.
+     */
+    @Deprecated
+    public boolean isInLockTaskMode() {
+        return getLockTaskModeState() != LOCK_TASK_MODE_NONE;
+    }
+
+    /**
+     * Return the current state of task locking. The three possible outcomes
+     * are {@link #LOCK_TASK_MODE_NONE}, {@link #LOCK_TASK_MODE_LOCKED}
+     * and {@link #LOCK_TASK_MODE_PINNED}.
+     *
+     * @see Activity#startLockTask()
+     */
+    public int getLockTaskModeState() {
+        try {
+            return getTaskService().getLockTaskModeState();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Enable more aggressive scheduling for latency-sensitive low-runtime VR threads. Only one
+     * thread can be a VR thread in a process at a time, and that thread may be subject to
+     * restrictions on the amount of time it can run.
+     *
+     * If persistent VR mode is set, whatever thread has been granted aggressive scheduling via this
+     * method will return to normal operation, and calling this method will do nothing while
+     * persistent VR mode is enabled.
+     *
+     * To reset the VR thread for an application, a tid of 0 can be passed.
+     *
+     * @see android.os.Process#myTid()
+     * @param tid tid of the VR thread
+     */
+    public static void setVrThread(int tid) {
+        try {
+            getTaskService().setVrThread(tid);
+        } catch (RemoteException e) {
+            // pass
+        }
+    }
+
+    /**
+     * Enable more aggressive scheduling for latency-sensitive low-runtime VR threads that persist
+     * beyond a single process. Only one thread can be a
+     * persistent VR thread at a time, and that thread may be subject to restrictions on the amount
+     * of time it can run. Calling this method will disable aggressive scheduling for non-persistent
+     * VR threads set via {@link #setVrThread}. If persistent VR mode is disabled then the
+     * persistent VR thread loses its new scheduling priority; this method must be called again to
+     * set the persistent thread.
+     *
+     * To reset the persistent VR thread, a tid of 0 can be passed.
+     *
+     * @see android.os.Process#myTid()
+     * @param tid tid of the VR thread
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.RESTRICTED_VR_ACCESS)
+    public static void setPersistentVrThread(int tid) {
+        try {
+            getService().setPersistentVrThread(tid);
+        } catch (RemoteException e) {
+            // pass
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.CHANGE_CONFIGURATION)
+    public void scheduleApplicationInfoChanged(List<String> packages, int userId) {
+        try {
+            getService().scheduleApplicationInfoChanged(packages, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * The AppTask allows you to manage your own application's tasks.
+     * See {@link android.app.ActivityManager#getAppTasks()}
+     */
+    public static class AppTask {
+        private IAppTask mAppTaskImpl;
+
+        /** @hide */
+        public AppTask(IAppTask task) {
+            mAppTaskImpl = task;
+        }
+
+        /**
+         * Finishes all activities in this task and removes it from the recent tasks list.
+         */
+        public void finishAndRemoveTask() {
+            try {
+                mAppTaskImpl.finishAndRemoveTask();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Get the RecentTaskInfo associated with this task.
+         *
+         * @return The RecentTaskInfo for this task, or null if the task no longer exists.
+         */
+        public RecentTaskInfo getTaskInfo() {
+            try {
+                return mAppTaskImpl.getTaskInfo();
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Bring this task to the foreground.  If it contains activities, they will be
+         * brought to the foreground with it and their instances re-created if needed.
+         * If it doesn't contain activities, the root activity of the task will be
+         * re-launched.
+         */
+        public void moveToFront() {
+            try {
+                ActivityThread thread = ActivityThread.currentActivityThread();
+                IApplicationThread appThread = thread.getApplicationThread();
+                String packageName = ActivityThread.currentPackageName();
+                mAppTaskImpl.moveToFront(appThread, packageName);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        /**
+         * Start an activity in this task.  Brings the task to the foreground.  If this task
+         * is not currently active (that is, its id < 0), then a new activity for the given
+         * Intent will be launched as the root of the task and the task brought to the
+         * foreground.  Otherwise, if this task is currently active and the Intent does not specify
+         * an activity to launch in a new task, then a new activity for the given Intent will
+         * be launched on top of the task and the task brought to the foreground.  If this
+         * task is currently active and the Intent specifies {@link Intent#FLAG_ACTIVITY_NEW_TASK}
+         * or would otherwise be launched in to a new task, then the activity not launched but
+         * this task be brought to the foreground and a new intent delivered to the top
+         * activity if appropriate.
+         *
+         * <p>In other words, you generally want to use an Intent here that does not specify
+         * {@link Intent#FLAG_ACTIVITY_NEW_TASK} or {@link Intent#FLAG_ACTIVITY_NEW_DOCUMENT},
+         * and let the system do the right thing.</p>
+         *
+         * @param intent The Intent describing the new activity to be launched on the task.
+         * @param options Optional launch options.
+         *
+         * @see Activity#startActivity(android.content.Intent, android.os.Bundle)
+         */
+        public void startActivity(Context context, Intent intent, Bundle options) {
+            ActivityThread thread = ActivityThread.currentActivityThread();
+            thread.getInstrumentation().execStartActivityFromAppTask(context,
+                    thread.getApplicationThread(), mAppTaskImpl, intent, options);
+        }
+
+        /**
+         * Modify the {@link Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} flag in the root
+         * Intent of this AppTask.
+         *
+         * @param exclude If true, {@link Intent#FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS} will
+         * be set; otherwise, it will be cleared.
+         */
+        public void setExcludeFromRecents(boolean exclude) {
+            try {
+                mAppTaskImpl.setExcludeFromRecents(exclude);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+}
diff --git a/android/app/ActivityManagerInternal.java b/android/app/ActivityManagerInternal.java
new file mode 100644
index 0000000..8508c2c
--- /dev/null
+++ b/android/app/ActivityManagerInternal.java
@@ -0,0 +1,359 @@
+/*
+ * 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.IIntentReceiver;
+import android.content.IIntentSender;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ActivityPresentationInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.UserInfo;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.os.TransactionTooLargeException;
+import android.view.RemoteAnimationAdapter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Activity manager local system service interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class ActivityManagerInternal {
+
+
+    // Access modes for handleIncomingUser.
+    public static final int ALLOW_NON_FULL = 0;
+    public static final int ALLOW_NON_FULL_IN_PROFILE = 1;
+    public static final int ALLOW_FULL_ONLY = 2;
+
+    /**
+     * Verify that calling app has access to the given provider.
+     */
+    public abstract String checkContentProviderAccess(String authority, int userId);
+
+    // Called by the power manager.
+    public abstract void onWakefulnessChanged(int wakefulness);
+
+    /**
+     * @return {@code true} if process start is successful, {@code false} otherwise.
+     */
+    public abstract boolean startIsolatedProcess(String entryPoint, String[] mainArgs,
+            String processName, String abiOverride, int uid, Runnable crashHandler);
+
+    /**
+     * Kill foreground apps from the specified user.
+     */
+    public abstract void killForegroundAppsForUser(int userHandle);
+
+    /**
+     *  Sets how long a {@link PendingIntent} can be temporarily whitelist to by bypass restrictions
+     *  such as Power Save mode.
+     */
+    public abstract void setPendingIntentWhitelistDuration(IIntentSender target,
+            IBinder whitelistToken, long duration);
+
+    /**
+     * Allows for a {@link PendingIntent} to be whitelisted to start activities from background.
+     */
+    public abstract void setPendingIntentAllowBgActivityStarts(
+            IIntentSender target, IBinder whitelistToken, int flags);
+
+    /**
+     * Voids {@link PendingIntent}'s privilege to be whitelisted to start activities from
+     * background.
+     */
+    public abstract void clearPendingIntentAllowBgActivityStarts(IIntentSender target,
+            IBinder whitelistToken);
+
+    /**
+     * Allow DeviceIdleController to tell us about what apps are whitelisted.
+     */
+    public abstract void setDeviceIdleWhitelist(int[] allAppids, int[] exceptIdleAppids);
+
+    /**
+     * Update information about which app IDs are on the temp whitelist.
+     */
+    public abstract void updateDeviceIdleTempWhitelist(int[] appids, int changingAppId,
+            boolean adding);
+
+    /**
+     * Get the procstate for the UID.  The return value will be between
+     * {@link ActivityManager#MIN_PROCESS_STATE} and {@link ActivityManager#MAX_PROCESS_STATE}.
+     * Note if the UID doesn't exist, it'll return {@link ActivityManager#PROCESS_STATE_NONEXISTENT}
+     * (-1).
+     */
+    public abstract int getUidProcessState(int uid);
+
+    /**
+     * @return {@code true} if system is ready, {@code false} otherwise.
+     */
+    public abstract boolean isSystemReady();
+
+    /**
+     * Sets if the given pid has an overlay UI or not.
+     *
+     * @param pid The pid we are setting overlay UI for.
+     * @param hasOverlayUi True if the process has overlay UI.
+     * @see android.view.WindowManager.LayoutParams#TYPE_APPLICATION_OVERLAY
+     */
+    public abstract void setHasOverlayUi(int pid, boolean hasOverlayUi);
+
+    /**
+     * Sets if the given pid is currently running a remote animation, which is taken a signal for
+     * determining oom adjustment and scheduling behavior.
+     *
+     * @param pid The pid we are setting overlay UI for.
+     * @param runningRemoteAnimation True if the process is running a remote animation, false
+     *                               otherwise.
+     * @see RemoteAnimationAdapter
+     */
+    public abstract void setRunningRemoteAnimation(int pid, boolean runningRemoteAnimation);
+
+    /**
+     * Called after the network policy rules are updated by
+     * {@link com.android.server.net.NetworkPolicyManagerService} for a specific {@param uid} and
+     * {@param procStateSeq}.
+     */
+    public abstract void notifyNetworkPolicyRulesUpdated(int uid, long procStateSeq);
+
+    /**
+     * @return true if runtime was restarted, false if it's normal boot
+     */
+    public abstract boolean isRuntimeRestarted();
+
+    /**
+     * Returns if more users can be started without stopping currently running users.
+     */
+    public abstract boolean canStartMoreUsers();
+
+    /**
+     * Sets the user switcher message for switching from {@link android.os.UserHandle#SYSTEM}.
+     */
+    public abstract void setSwitchingFromSystemUserMessage(String switchingFromSystemUserMessage);
+
+    /**
+     * Sets the user switcher message for switching to {@link android.os.UserHandle#SYSTEM}.
+     */
+    public abstract void setSwitchingToSystemUserMessage(String switchingToSystemUserMessage);
+
+    /**
+     * Returns maximum number of users that can run simultaneously.
+     */
+    public abstract int getMaxRunningUsers();
+
+    /**
+     * Whether an UID is active or idle.
+     */
+    public abstract boolean isUidActive(int uid);
+
+    /**
+     * Returns a list of running processes along with corresponding uids, pids and their oom score.
+     *
+     * Only processes managed by ActivityManagerService are included.
+     */
+    public abstract List<ProcessMemoryState> getMemoryStateForProcesses();
+
+    /**
+     * Checks to see if the calling pid is allowed to handle the user. Returns adjusted user id as
+     * needed.
+     */
+    public abstract int handleIncomingUser(int callingPid, int callingUid, int userId,
+            boolean allowAll, int allowMode, String name, String callerPackage);
+
+    /** Checks if the calling binder pid as the permission. */
+    public abstract void enforceCallingPermission(String permission, String func);
+
+    /** Returns the current user id. */
+    public abstract int getCurrentUserId();
+
+    /** Returns true if the user is running. */
+    public abstract boolean isUserRunning(int userId, int flags);
+
+    /** Trims memory usage in the system by removing/stopping unused application processes. */
+    public abstract void trimApplications();
+
+    /** Kill the processes in the list due to their tasks been removed. */
+    public abstract void killProcessesForRemovedTask(ArrayList<Object> procsToKill);
+
+    /** Kill the process immediately. */
+    public abstract void killProcess(String processName, int uid, String reason);
+
+    /**
+     * Returns {@code true} if {@code uid} is running an activity from {@code packageName}.
+     */
+    public abstract boolean hasRunningActivity(int uid, @Nullable String packageName);
+
+    public abstract void updateOomAdj();
+    public abstract void updateCpuStats();
+
+    /**
+     * Update battery stats on activity usage.
+     * @param activity
+     * @param uid
+     * @param userId
+     * @param started
+     */
+    public abstract void updateBatteryStats(
+            ComponentName activity, int uid, int userId, boolean resumed);
+
+    /**
+     * Update UsageStats of the activity.
+     * @param activity
+     * @param userId
+     * @param event
+     * @param appToken ActivityRecord's appToken.
+     * @param taskRoot TaskRecord's root
+     */
+    public abstract void updateActivityUsageStats(
+            ComponentName activity, int userId, int event, IBinder appToken,
+            ComponentName taskRoot);
+    public abstract void updateForegroundTimeIfOnBattery(
+            String packageName, int uid, long cpuTimeDiff);
+    public abstract void sendForegroundProfileChanged(int userId);
+
+    /**
+     * Returns whether the given user requires credential entry at this time. This is used to
+     * intercept activity launches for work apps when the Work Challenge is present.
+     */
+    public abstract boolean shouldConfirmCredentials(int userId);
+
+    public abstract int[] getCurrentProfileIds();
+    public abstract UserInfo getCurrentUser();
+    public abstract void ensureNotSpecialUser(int userId);
+    public abstract boolean isCurrentProfile(int userId);
+    public abstract boolean hasStartedUserState(int userId);
+    public abstract void finishUserSwitch(Object uss);
+
+    /** Schedule the execution of all pending app GCs. */
+    public abstract void scheduleAppGcs();
+
+    /** Gets the task id for a given activity. */
+    public abstract int getTaskIdForActivity(@NonNull IBinder token, boolean onlyRoot);
+
+    /** Gets the basic info for a given activity. */
+    public abstract ActivityPresentationInfo getActivityPresentationInfo(@NonNull IBinder token);
+
+    public abstract void setBooting(boolean booting);
+    public abstract boolean isBooting();
+    public abstract void setBooted(boolean booted);
+    public abstract boolean isBooted();
+    public abstract void finishBooting();
+
+    public abstract void tempWhitelistForPendingIntent(int callerPid, int callerUid, int targetUid,
+            long duration, String tag);
+    public abstract int broadcastIntentInPackage(String packageName, int uid, int realCallingUid,
+            int realCallingPid, Intent intent, String resolvedType, IIntentReceiver resultTo,
+            int resultCode, String resultData, Bundle resultExtras, String requiredPermission,
+            Bundle bOptions, boolean serialized, boolean sticky, int userId,
+            boolean allowBackgroundActivityStarts);
+    public abstract ComponentName startServiceInPackage(int uid, Intent service,
+            String resolvedType, boolean fgRequired, String callingPackage, int userId,
+            boolean allowBackgroundActivityStarts) throws TransactionTooLargeException;
+
+    public abstract void disconnectActivityFromServices(Object connectionHolder, Object conns);
+    public abstract void cleanUpServices(int userId, ComponentName component, Intent baseIntent);
+    public abstract ActivityInfo getActivityInfoForUser(ActivityInfo aInfo, int userId);
+    public abstract void ensureBootCompleted();
+    public abstract void updateOomLevelsForDisplay(int displayId);
+    public abstract boolean isActivityStartsLoggingEnabled();
+    /** Returns true if the background activity starts is enabled. */
+    public abstract boolean isBackgroundActivityStartsEnabled();
+    public abstract void reportCurKeyguardUsageEvent(boolean keyguardShowing);
+
+    /** Input dispatch timeout to a window, start the ANR process. */
+    public abstract long inputDispatchingTimedOut(int pid, boolean aboveSystem, String reason);
+    public abstract boolean inputDispatchingTimedOut(Object proc, String activityShortComponentName,
+            ApplicationInfo aInfo, String parentShortComponentName, Object parentProc,
+            boolean aboveSystem, String reason);
+
+    /**
+     * Sends {@link android.content.Intent#ACTION_CONFIGURATION_CHANGED} with all the appropriate
+     * flags.
+     */
+    public abstract void broadcastGlobalConfigurationChanged(int changes, boolean initLocale);
+
+    /**
+     * Sends {@link android.content.Intent#ACTION_CLOSE_SYSTEM_DIALOGS} with all the appropriate
+     * flags.
+     */
+    public abstract void broadcastCloseSystemDialogs(String reason);
+
+    /**
+     * Kills all background processes, except those matching any of the specified properties.
+     *
+     * @param minTargetSdk the target SDK version at or above which to preserve processes,
+     *                     or {@code -1} to ignore the target SDK
+     * @param maxProcState the process state at or below which to preserve processes,
+     *                     or {@code -1} to ignore the process state
+     */
+    public abstract void killAllBackgroundProcessesExcept(int minTargetSdk, int maxProcState);
+
+    /** Starts a given process. */
+    public abstract void startProcess(String processName, ApplicationInfo info,
+            boolean knownToBeDead, String hostingType, ComponentName hostingName);
+
+    /** Starts up the starting activity process for debugging if needed.
+     * This function needs to be called synchronously from WindowManager context so the caller
+     * passes a lock {@code wmLock} and waits to be notified.
+     *
+     * @param wmLock calls {@code notify} on the object to wake up the caller.
+    */
+    public abstract void setDebugFlagsForStartingActivity(ActivityInfo aInfo, int startFlags,
+            ProfilerInfo profilerInfo, Object wmLock);
+
+    /** Returns mount mode for process running with given pid */
+    public abstract int getStorageMountMode(int pid, int uid);
+
+    /** Returns true if the given uid is the app in the foreground. */
+    public abstract boolean isAppForeground(int uid);
+
+    /** Returns true if the given uid is currently marked 'bad' */
+    public abstract boolean isAppBad(ApplicationInfo info);
+
+    /** Remove pending backup for the given userId. */
+    public abstract void clearPendingBackup(int userId);
+
+    /**
+     * When power button is very long pressed, call this interface to do some pre-shutdown work
+     * like persisting database etc.
+     */
+    public abstract void prepareForPossibleShutdown();
+
+    /**
+     * Returns {@code true} if {@code uid} is running a foreground service of a specific
+     * {@code foregroundServiceType}.
+     */
+    public abstract boolean hasRunningForegroundService(int uid, int foregroundServiceType);
+
+    /**
+     * Registers the specified {@code processObserver} to be notified of future changes to
+     * process state.
+     */
+    public abstract void registerProcessObserver(IProcessObserver processObserver);
+
+    /**
+     * Unregisters the specified {@code processObserver}.
+     */
+    public abstract void unregisterProcessObserver(IProcessObserver processObserver);
+}
diff --git a/android/app/ActivityManagerNative.java b/android/app/ActivityManagerNative.java
new file mode 100644
index 0000000..37509e1
--- /dev/null
+++ b/android/app/ActivityManagerNative.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Intent;
+import android.os.IBinder;
+
+/**
+ * {@hide}
+ * @deprecated will be removed soon. See individual methods for alternatives.
+ */
+@Deprecated
+public abstract class ActivityManagerNative {
+    /**
+     * Cast a Binder object into an activity manager interface, generating
+     * a proxy if needed.
+     *
+     * @deprecated use IActivityManager.Stub.asInterface instead.
+     */
+    @UnsupportedAppUsage
+    static public IActivityManager asInterface(IBinder obj) {
+        return IActivityManager.Stub.asInterface(obj);
+    }
+
+    /**
+     * Retrieve the system's default/global activity manager.
+     *
+     * @deprecated use ActivityManager.getService instead.
+     */
+    @UnsupportedAppUsage
+    static public IActivityManager getDefault() {
+        return ActivityManager.getService();
+    }
+
+    /**
+     * Convenience for checking whether the system is ready.  For internal use only.
+     *
+     * @deprecated use ActivityManagerInternal.isSystemReady instead.
+     */
+    @UnsupportedAppUsage
+    static public boolean isSystemReady() {
+        return ActivityManager.isSystemReady();
+    }
+
+    /**
+     * @deprecated use ActivityManager.broadcastStickyIntent instead.
+     */
+    @UnsupportedAppUsage
+    static public void broadcastStickyIntent(Intent intent, String permission, int userId) {
+        broadcastStickyIntent(intent, permission, AppOpsManager.OP_NONE, userId);
+    }
+
+    /**
+     * Convenience for sending a sticky broadcast.  For internal use only.
+     * If you don't care about permission, use null.
+     *
+     * @deprecated use ActivityManager.broadcastStickyIntent instead.
+     */
+    static public void broadcastStickyIntent(Intent intent, String permission, int appOp,
+            int userId) {
+        ActivityManager.broadcastStickyIntent(intent, appOp, userId);
+    }
+
+    /**
+     * @deprecated use ActivityManager.noteWakeupAlarm instead.
+     */
+    static public void noteWakeupAlarm(PendingIntent ps, int sourceUid, String sourcePkg,
+            String tag) {
+        ActivityManager.noteWakeupAlarm(ps, null, sourceUid, sourcePkg, tag);
+    }
+
+    /**
+     * @deprecated use ActivityManager.noteAlarmStart instead.
+     */
+    static public void noteAlarmStart(PendingIntent ps, int sourceUid, String tag) {
+        ActivityManager.noteAlarmStart(ps, null, sourceUid, tag);
+    }
+
+    /**
+     * @deprecated use ActivityManager.noteAlarmFinish instead.
+     */
+    static public void noteAlarmFinish(PendingIntent ps, int sourceUid, String tag) {
+        ActivityManager.noteAlarmFinish(ps, null, sourceUid, tag);
+    }
+}
diff --git a/android/app/ActivityOptions.java b/android/app/ActivityOptions.java
new file mode 100644
index 0000000..926044b
--- /dev/null
+++ b/android/app/ActivityOptions.java
@@ -0,0 +1,1716 @@
+/*
+ * 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.app;
+
+import static android.Manifest.permission.CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS;
+import static android.app.ActivityTaskManager.SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.graphics.Bitmap.Config;
+import android.graphics.GraphicBuffer;
+import android.graphics.Rect;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IRemoteCallback;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.UserHandle;
+import android.transition.Transition;
+import android.transition.TransitionListenerAdapter;
+import android.transition.TransitionManager;
+import android.util.Pair;
+import android.util.Slog;
+import android.view.AppTransitionAnimationSpec;
+import android.view.IAppTransitionAnimationSpecsFuture;
+import android.view.RemoteAnimationAdapter;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import java.util.ArrayList;
+
+/**
+ * Helper class for building an options Bundle that can be used with
+ * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
+ * Context.startActivity(Intent, Bundle)} and related methods.
+ */
+public class ActivityOptions {
+    private static final String TAG = "ActivityOptions";
+
+    /**
+     * A long in the extras delivered by {@link #requestUsageTimeReport} that contains
+     * the total time (in ms) the user spent in the app flow.
+     */
+    public static final String EXTRA_USAGE_TIME_REPORT = "android.activity.usage_time";
+
+    /**
+     * A Bundle in the extras delivered by {@link #requestUsageTimeReport} that contains
+     * detailed information about the time spent in each package associated with the app;
+     * each key is a package name, whose value is a long containing the time (in ms).
+     */
+    public static final String EXTRA_USAGE_TIME_REPORT_PACKAGES = "android.usage_time_packages";
+
+    /**
+     * The package name that created the options.
+     * @hide
+     */
+    public static final String KEY_PACKAGE_NAME = "android:activity.packageName";
+
+    /**
+     * The bounds (window size) that the activity should be launched in. Set to null explicitly for
+     * full screen. If the key is not found, previous bounds will be preserved.
+     * NOTE: This value is ignored on devices that don't have
+     * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} or
+     * {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled.
+     * @hide
+     */
+    public static final String KEY_LAUNCH_BOUNDS = "android:activity.launchBounds";
+
+    /**
+     * Type of animation that arguments specify.
+     * @hide
+     */
+    public static final String KEY_ANIM_TYPE = "android:activity.animType";
+
+    /**
+     * Custom enter animation resource ID.
+     * @hide
+     */
+    public static final String KEY_ANIM_ENTER_RES_ID = "android:activity.animEnterRes";
+
+    /**
+     * Custom exit animation resource ID.
+     * @hide
+     */
+    public static final String KEY_ANIM_EXIT_RES_ID = "android:activity.animExitRes";
+
+    /**
+     * Custom in-place animation resource ID.
+     * @hide
+     */
+    public static final String KEY_ANIM_IN_PLACE_RES_ID = "android:activity.animInPlaceRes";
+
+    /**
+     * Bitmap for thumbnail animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_THUMBNAIL = "android:activity.animThumbnail";
+
+    /**
+     * Start X position of thumbnail animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_START_X = "android:activity.animStartX";
+
+    /**
+     * Start Y position of thumbnail animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_START_Y = "android:activity.animStartY";
+
+    /**
+     * Initial width of the animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_WIDTH = "android:activity.animWidth";
+
+    /**
+     * Initial height of the animation.
+     * @hide
+     */
+    public static final String KEY_ANIM_HEIGHT = "android:activity.animHeight";
+
+    /**
+     * Callback for when animation is started.
+     * @hide
+     */
+    public static final String KEY_ANIM_START_LISTENER = "android:activity.animStartListener";
+
+    /**
+     * Callback for when the last frame of the animation is played.
+     * @hide
+     */
+    private static final String KEY_ANIMATION_FINISHED_LISTENER =
+            "android:activity.animationFinishedListener";
+
+    /**
+     * Descriptions of app transition animations to be played during the activity launch.
+     */
+    private static final String KEY_ANIM_SPECS = "android:activity.animSpecs";
+
+    /**
+     * Whether the activity should be launched into LockTask mode.
+     * @see #setLockTaskEnabled(boolean)
+     */
+    private static final String KEY_LOCK_TASK_MODE = "android:activity.lockTaskMode";
+
+    /**
+     * The display id the activity should be launched into.
+     * @see #setLaunchDisplayId(int)
+     * @hide
+     */
+    private static final String KEY_LAUNCH_DISPLAY_ID = "android.activity.launchDisplayId";
+
+    /**
+     * The windowing mode the activity should be launched into.
+     * @hide
+     */
+    private static final String KEY_LAUNCH_WINDOWING_MODE = "android.activity.windowingMode";
+
+    /**
+     * The activity type the activity should be launched as.
+     * @hide
+     */
+    private static final String KEY_LAUNCH_ACTIVITY_TYPE = "android.activity.activityType";
+
+    /**
+     * The task id the activity should be launched into.
+     * @hide
+     */
+    private static final String KEY_LAUNCH_TASK_ID = "android.activity.launchTaskId";
+
+    /**
+     * See {@link #setPendingIntentLaunchFlags(int)}
+     * @hide
+     */
+    private static final String KEY_PENDING_INTENT_LAUNCH_FLAGS =
+            "android.activity.pendingIntentLaunchFlags";
+
+    /**
+     * See {@link #setTaskOverlay}.
+     * @hide
+     */
+    private static final String KEY_TASK_OVERLAY = "android.activity.taskOverlay";
+
+    /**
+     * See {@link #setTaskOverlay}.
+     * @hide
+     */
+    private static final String KEY_TASK_OVERLAY_CAN_RESUME =
+            "android.activity.taskOverlayCanResume";
+
+    /**
+     * See {@link #setAvoidMoveToFront()}.
+     * @hide
+     */
+    private static final String KEY_AVOID_MOVE_TO_FRONT = "android.activity.avoidMoveToFront";
+
+    /**
+     * See {@link #setFreezeRecentTasksReordering()}.
+     * @hide
+     */
+    private static final String KEY_FREEZE_RECENT_TASKS_REORDERING =
+            "android.activity.freezeRecentTasksReordering";
+
+    /**
+     * Where the split-screen-primary stack should be positioned.
+     * @hide
+     */
+    private static final String KEY_SPLIT_SCREEN_CREATE_MODE =
+            "android:activity.splitScreenCreateMode";
+
+    /**
+     * Determines whether to disallow the outgoing activity from entering picture-in-picture as the
+     * result of a new activity being launched.
+     * @hide
+     */
+    private static final String KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING =
+            "android:activity.disallowEnterPictureInPictureWhileLaunching";
+
+    /**
+     * For Activity transitions, the calling Activity's TransitionListener used to
+     * notify the called Activity when the shared element and the exit transitions
+     * complete.
+     */
+    private static final String KEY_TRANSITION_COMPLETE_LISTENER
+            = "android:activity.transitionCompleteListener";
+
+    private static final String KEY_TRANSITION_IS_RETURNING
+            = "android:activity.transitionIsReturning";
+    private static final String KEY_TRANSITION_SHARED_ELEMENTS
+            = "android:activity.sharedElementNames";
+    private static final String KEY_RESULT_DATA = "android:activity.resultData";
+    private static final String KEY_RESULT_CODE = "android:activity.resultCode";
+    private static final String KEY_EXIT_COORDINATOR_INDEX
+            = "android:activity.exitCoordinatorIndex";
+
+    private static final String KEY_USAGE_TIME_REPORT = "android:activity.usageTimeReport";
+    private static final String KEY_ROTATION_ANIMATION_HINT = "android:activity.rotationAnimationHint";
+
+    private static final String KEY_INSTANT_APP_VERIFICATION_BUNDLE
+            = "android:instantapps.installerbundle";
+    private static final String KEY_SPECS_FUTURE = "android:activity.specsFuture";
+    private static final String KEY_REMOTE_ANIMATION_ADAPTER
+            = "android:activity.remoteAnimationAdapter";
+
+    /** @hide */
+    public static final int ANIM_NONE = 0;
+    /** @hide */
+    public static final int ANIM_CUSTOM = 1;
+    /** @hide */
+    public static final int ANIM_SCALE_UP = 2;
+    /** @hide */
+    public static final int ANIM_THUMBNAIL_SCALE_UP = 3;
+    /** @hide */
+    public static final int ANIM_THUMBNAIL_SCALE_DOWN = 4;
+    /** @hide */
+    public static final int ANIM_SCENE_TRANSITION = 5;
+    /** @hide */
+    public static final int ANIM_DEFAULT = 6;
+    /** @hide */
+    public static final int ANIM_LAUNCH_TASK_BEHIND = 7;
+    /** @hide */
+    public static final int ANIM_THUMBNAIL_ASPECT_SCALE_UP = 8;
+    /** @hide */
+    public static final int ANIM_THUMBNAIL_ASPECT_SCALE_DOWN = 9;
+    /** @hide */
+    public static final int ANIM_CUSTOM_IN_PLACE = 10;
+    /** @hide */
+    public static final int ANIM_CLIP_REVEAL = 11;
+    /** @hide */
+    public static final int ANIM_OPEN_CROSS_PROFILE_APPS = 12;
+    /** @hide */
+    public static final int ANIM_REMOTE_ANIMATION = 13;
+
+    private String mPackageName;
+    private Rect mLaunchBounds;
+    private int mAnimationType = ANIM_NONE;
+    private int mCustomEnterResId;
+    private int mCustomExitResId;
+    private int mCustomInPlaceResId;
+    private Bitmap mThumbnail;
+    private int mStartX;
+    private int mStartY;
+    private int mWidth;
+    private int mHeight;
+    private IRemoteCallback mAnimationStartedListener;
+    private IRemoteCallback mAnimationFinishedListener;
+    private ResultReceiver mTransitionReceiver;
+    private boolean mIsReturning;
+    private ArrayList<String> mSharedElementNames;
+    private Intent mResultData;
+    private int mResultCode;
+    private int mExitCoordinatorIndex;
+    private PendingIntent mUsageTimeReport;
+    private int mLaunchDisplayId = INVALID_DISPLAY;
+    @WindowConfiguration.WindowingMode
+    private int mLaunchWindowingMode = WINDOWING_MODE_UNDEFINED;
+    @WindowConfiguration.ActivityType
+    private int mLaunchActivityType = ACTIVITY_TYPE_UNDEFINED;
+    private int mLaunchTaskId = -1;
+    private int mPendingIntentLaunchFlags;
+    private int mSplitScreenCreateMode = SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT;
+    private boolean mLockTaskMode = false;
+    private boolean mDisallowEnterPictureInPictureWhileLaunching;
+    private boolean mTaskOverlay;
+    private boolean mTaskOverlayCanResume;
+    private boolean mAvoidMoveToFront;
+    private boolean mFreezeRecentTasksReordering;
+    private AppTransitionAnimationSpec mAnimSpecs[];
+    private int mRotationAnimationHint = -1;
+    private Bundle mAppVerificationBundle;
+    private IAppTransitionAnimationSpecsFuture mSpecsFuture;
+    private RemoteAnimationAdapter mRemoteAnimationAdapter;
+
+    /**
+     * Create an ActivityOptions specifying a custom animation to run when
+     * the activity is displayed.
+     *
+     * @param context Who is defining this.  This is the application that the
+     * animation resources will be loaded from.
+     * @param enterResId A resource ID of the animation resource to use for
+     * the incoming activity.  Use 0 for no animation.
+     * @param exitResId A resource ID of the animation resource to use for
+     * the outgoing activity.  Use 0 for no animation.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     */
+    public static ActivityOptions makeCustomAnimation(Context context,
+            int enterResId, int exitResId) {
+        return makeCustomAnimation(context, enterResId, exitResId, null, null);
+    }
+
+    /**
+     * Create an ActivityOptions specifying a custom animation to run when
+     * the activity is displayed.
+     *
+     * @param context Who is defining this.  This is the application that the
+     * animation resources will be loaded from.
+     * @param enterResId A resource ID of the animation resource to use for
+     * the incoming activity.  Use 0 for no animation.
+     * @param exitResId A resource ID of the animation resource to use for
+     * the outgoing activity.  Use 0 for no animation.
+     * @param handler If <var>listener</var> is non-null this must be a valid
+     * Handler on which to dispatch the callback; otherwise it should be null.
+     * @param listener Optional OnAnimationStartedListener to find out when the
+     * requested animation has started running.  If for some reason the animation
+     * is not executed, the callback will happen immediately.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static ActivityOptions makeCustomAnimation(Context context,
+            int enterResId, int exitResId, Handler handler, OnAnimationStartedListener listener) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = context.getPackageName();
+        opts.mAnimationType = ANIM_CUSTOM;
+        opts.mCustomEnterResId = enterResId;
+        opts.mCustomExitResId = exitResId;
+        opts.setOnAnimationStartedListener(handler, listener);
+        return opts;
+    }
+
+    /**
+     * Creates an ActivityOptions specifying a custom animation to run in place on an existing
+     * activity.
+     *
+     * @param context Who is defining this.  This is the application that the
+     * animation resources will be loaded from.
+     * @param animId A resource ID of the animation resource to use for
+     * the incoming activity.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when running an in-place animation.
+     * @hide
+     */
+    public static ActivityOptions makeCustomInPlaceAnimation(Context context, int animId) {
+        if (animId == 0) {
+            throw new RuntimeException("You must specify a valid animation.");
+        }
+
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = context.getPackageName();
+        opts.mAnimationType = ANIM_CUSTOM_IN_PLACE;
+        opts.mCustomInPlaceResId = animId;
+        return opts;
+    }
+
+    private void setOnAnimationStartedListener(final Handler handler,
+            final OnAnimationStartedListener listener) {
+        if (listener != null) {
+            mAnimationStartedListener = new IRemoteCallback.Stub() {
+                @Override
+                public void sendResult(Bundle data) throws RemoteException {
+                    handler.post(new Runnable() {
+                        @Override public void run() {
+                            listener.onAnimationStarted();
+                        }
+                    });
+                }
+            };
+        }
+    }
+
+    /**
+     * Callback for use with {@link ActivityOptions#makeThumbnailScaleUpAnimation}
+     * to find out when the given animation has started running.
+     * @hide
+     */
+    public interface OnAnimationStartedListener {
+        void onAnimationStarted();
+    }
+
+    private void setOnAnimationFinishedListener(final Handler handler,
+            final OnAnimationFinishedListener listener) {
+        if (listener != null) {
+            mAnimationFinishedListener = new IRemoteCallback.Stub() {
+                @Override
+                public void sendResult(Bundle data) throws RemoteException {
+                    handler.post(new Runnable() {
+                        @Override
+                        public void run() {
+                            listener.onAnimationFinished();
+                        }
+                    });
+                }
+            };
+        }
+    }
+
+    /**
+     * Callback for use with {@link ActivityOptions#makeThumbnailAspectScaleDownAnimation}
+     * to find out when the given animation has drawn its last frame.
+     * @hide
+     */
+    public interface OnAnimationFinishedListener {
+        void onAnimationFinished();
+    }
+
+    /**
+     * Create an ActivityOptions specifying an animation where the new
+     * activity is scaled from a small originating area of the screen to
+     * its final full representation.
+     *
+     * <p>If the Intent this is being used with has not set its
+     * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds},
+     * those bounds will be filled in for you based on the initial
+     * bounds passed in here.
+     *
+     * @param source The View that the new activity is animating from.  This
+     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+     * @param startX The x starting location of the new activity, relative to <var>source</var>.
+     * @param startY The y starting location of the activity, relative to <var>source</var>.
+     * @param width The initial width of the new activity.
+     * @param height The initial height of the new activity.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     */
+    public static ActivityOptions makeScaleUpAnimation(View source,
+            int startX, int startY, int width, int height) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = source.getContext().getPackageName();
+        opts.mAnimationType = ANIM_SCALE_UP;
+        int[] pts = new int[2];
+        source.getLocationOnScreen(pts);
+        opts.mStartX = pts[0] + startX;
+        opts.mStartY = pts[1] + startY;
+        opts.mWidth = width;
+        opts.mHeight = height;
+        return opts;
+    }
+
+    /**
+     * Create an ActivityOptions specifying an animation where the new
+     * activity is revealed from a small originating area of the screen to
+     * its final full representation.
+     *
+     * @param source The View that the new activity is animating from.  This
+     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+     * @param startX The x starting location of the new activity, relative to <var>source</var>.
+     * @param startY The y starting location of the activity, relative to <var>source</var>.
+     * @param width The initial width of the new activity.
+     * @param height The initial height of the new activity.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     */
+    public static ActivityOptions makeClipRevealAnimation(View source,
+            int startX, int startY, int width, int height) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mAnimationType = ANIM_CLIP_REVEAL;
+        int[] pts = new int[2];
+        source.getLocationOnScreen(pts);
+        opts.mStartX = pts[0] + startX;
+        opts.mStartY = pts[1] + startY;
+        opts.mWidth = width;
+        opts.mHeight = height;
+        return opts;
+    }
+
+    /**
+     * Creates an {@link ActivityOptions} object specifying an animation where the new activity
+     * is started in another user profile by calling {@link
+     * android.content.pm.crossprofile.CrossProfileApps#startMainActivity(ComponentName, UserHandle)
+     * }.
+     * @hide
+     */
+    public static ActivityOptions makeOpenCrossProfileAppsAnimation() {
+        ActivityOptions options = new ActivityOptions();
+        options.mAnimationType = ANIM_OPEN_CROSS_PROFILE_APPS;
+        return options;
+    }
+
+    /**
+     * Create an ActivityOptions specifying an animation where a thumbnail
+     * is scaled from a given position to the new activity window that is
+     * being started.
+     *
+     * <p>If the Intent this is being used with has not set its
+     * {@link android.content.Intent#setSourceBounds Intent.setSourceBounds},
+     * those bounds will be filled in for you based on the initial
+     * thumbnail location and size provided here.
+     *
+     * @param source The View that this thumbnail is animating from.  This
+     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+     * @param thumbnail The bitmap that will be shown as the initial thumbnail
+     * of the animation.
+     * @param startX The x starting location of the bitmap, relative to <var>source</var>.
+     * @param startY The y starting location of the bitmap, relative to <var>source</var>.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     */
+    public static ActivityOptions makeThumbnailScaleUpAnimation(View source,
+            Bitmap thumbnail, int startX, int startY) {
+        return makeThumbnailScaleUpAnimation(source, thumbnail, startX, startY, null);
+    }
+
+    /**
+     * Create an ActivityOptions specifying an animation where a thumbnail
+     * is scaled from a given position to the new activity window that is
+     * being started.
+     *
+     * @param source The View that this thumbnail is animating from.  This
+     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+     * @param thumbnail The bitmap that will be shown as the initial thumbnail
+     * of the animation.
+     * @param startX The x starting location of the bitmap, relative to <var>source</var>.
+     * @param startY The y starting location of the bitmap, relative to <var>source</var>.
+     * @param listener Optional OnAnimationStartedListener to find out when the
+     * requested animation has started running.  If for some reason the animation
+     * is not executed, the callback will happen immediately.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     */
+    private static ActivityOptions makeThumbnailScaleUpAnimation(View source,
+            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener) {
+        return makeThumbnailAnimation(source, thumbnail, startX, startY, listener, true);
+    }
+
+    private static ActivityOptions makeThumbnailAnimation(View source,
+            Bitmap thumbnail, int startX, int startY, OnAnimationStartedListener listener,
+            boolean scaleUp) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = source.getContext().getPackageName();
+        opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_SCALE_UP : ANIM_THUMBNAIL_SCALE_DOWN;
+        opts.mThumbnail = thumbnail;
+        int[] pts = new int[2];
+        source.getLocationOnScreen(pts);
+        opts.mStartX = pts[0] + startX;
+        opts.mStartY = pts[1] + startY;
+        opts.setOnAnimationStartedListener(source.getHandler(), listener);
+        return opts;
+    }
+
+    /**
+     * Create an ActivityOptions specifying an animation where a list of activity windows and
+     * thumbnails are aspect scaled to/from a new location.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static ActivityOptions makeMultiThumbFutureAspectScaleAnimation(Context context,
+            Handler handler, IAppTransitionAnimationSpecsFuture specsFuture,
+            OnAnimationStartedListener listener, boolean scaleUp) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = context.getPackageName();
+        opts.mAnimationType = scaleUp
+                ? ANIM_THUMBNAIL_ASPECT_SCALE_UP
+                : ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
+        opts.mSpecsFuture = specsFuture;
+        opts.setOnAnimationStartedListener(handler, listener);
+        return opts;
+    }
+
+    /**
+     * Create an ActivityOptions specifying an animation where the new activity
+     * window and a thumbnail is aspect-scaled to a new location.
+     *
+     * @param source The View that this thumbnail is animating to.  This
+     * defines the coordinate space for <var>startX</var> and <var>startY</var>.
+     * @param thumbnail The bitmap that will be shown as the final thumbnail
+     * of the animation.
+     * @param startX The x end location of the bitmap, relative to <var>source</var>.
+     * @param startY The y end location of the bitmap, relative to <var>source</var>.
+     * @param handler If <var>listener</var> is non-null this must be a valid
+     * Handler on which to dispatch the callback; otherwise it should be null.
+     * @param listener Optional OnAnimationStartedListener to find out when the
+     * requested animation has started running.  If for some reason the animation
+     * is not executed, the callback will happen immediately.
+     * @return Returns a new ActivityOptions object that you can use to
+     * supply these options as the options Bundle when starting an activity.
+     * @hide
+     */
+    public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source,
+            Bitmap thumbnail, int startX, int startY, int targetWidth, int targetHeight,
+            Handler handler, OnAnimationStartedListener listener) {
+        return makeAspectScaledThumbnailAnimation(source, thumbnail, startX, startY,
+                targetWidth, targetHeight, handler, listener, false);
+    }
+
+    private static ActivityOptions makeAspectScaledThumbnailAnimation(View source, Bitmap thumbnail,
+            int startX, int startY, int targetWidth, int targetHeight,
+            Handler handler, OnAnimationStartedListener listener, boolean scaleUp) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = source.getContext().getPackageName();
+        opts.mAnimationType = scaleUp ? ANIM_THUMBNAIL_ASPECT_SCALE_UP :
+                ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
+        opts.mThumbnail = thumbnail;
+        int[] pts = new int[2];
+        source.getLocationOnScreen(pts);
+        opts.mStartX = pts[0] + startX;
+        opts.mStartY = pts[1] + startY;
+        opts.mWidth = targetWidth;
+        opts.mHeight = targetHeight;
+        opts.setOnAnimationStartedListener(handler, listener);
+        return opts;
+    }
+
+    /** @hide */
+    public static ActivityOptions makeThumbnailAspectScaleDownAnimation(View source,
+            AppTransitionAnimationSpec[] specs, Handler handler,
+            OnAnimationStartedListener onAnimationStartedListener,
+            OnAnimationFinishedListener onAnimationFinishedListener) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mPackageName = source.getContext().getPackageName();
+        opts.mAnimationType = ANIM_THUMBNAIL_ASPECT_SCALE_DOWN;
+        opts.mAnimSpecs = specs;
+        opts.setOnAnimationStartedListener(handler, onAnimationStartedListener);
+        opts.setOnAnimationFinishedListener(handler, onAnimationFinishedListener);
+        return opts;
+    }
+
+    /**
+     * Create an ActivityOptions to transition between Activities using cross-Activity scene
+     * animations. This method carries the position of one shared element to the started Activity.
+     * The position of <code>sharedElement</code> will be used as the epicenter for the
+     * exit Transition. The position of the shared element in the launched Activity will be the
+     * epicenter of its entering Transition.
+     *
+     * <p>This requires {@link android.view.Window#FEATURE_ACTIVITY_TRANSITIONS} to be
+     * enabled on the calling Activity to cause an exit transition. The same must be in
+     * the called Activity to get an entering transition.</p>
+     * @param activity The Activity whose window contains the shared elements.
+     * @param sharedElement The View to transition to the started Activity.
+     * @param sharedElementName The shared element name as used in the target Activity. This
+     *                          must not be null.
+     * @return Returns a new ActivityOptions object that you can use to
+     *         supply these options as the options Bundle when starting an activity.
+     * @see android.transition.Transition#setEpicenterCallback(
+     *          android.transition.Transition.EpicenterCallback)
+     */
+    public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
+            View sharedElement, String sharedElementName) {
+        return makeSceneTransitionAnimation(activity, Pair.create(sharedElement, sharedElementName));
+    }
+
+    /**
+     * Create an ActivityOptions to transition between Activities using cross-Activity scene
+     * animations. This method carries the position of multiple shared elements to the started
+     * Activity. The position of the first element in sharedElements
+     * will be used as the epicenter for the exit Transition. The position of the associated
+     * shared element in the launched Activity will be the epicenter of its entering Transition.
+     *
+     * <p>This requires {@link android.view.Window#FEATURE_ACTIVITY_TRANSITIONS} to be
+     * enabled on the calling Activity to cause an exit transition. The same must be in
+     * the called Activity to get an entering transition.</p>
+     * @param activity The Activity whose window contains the shared elements.
+     * @param sharedElements The names of the shared elements to transfer to the called
+     *                       Activity and their associated Views. The Views must each have
+     *                       a unique shared element name.
+     * @return Returns a new ActivityOptions object that you can use to
+     *         supply these options as the options Bundle when starting an activity.
+     * @see android.transition.Transition#setEpicenterCallback(
+     *          android.transition.Transition.EpicenterCallback)
+     */
+    @SafeVarargs
+    public static ActivityOptions makeSceneTransitionAnimation(Activity activity,
+            Pair<View, String>... sharedElements) {
+        ActivityOptions opts = new ActivityOptions();
+        makeSceneTransitionAnimation(activity, activity.getWindow(), opts,
+                activity.mExitTransitionListener, sharedElements);
+        return opts;
+    }
+
+    /**
+     * Call this immediately prior to startActivity to begin a shared element transition
+     * from a non-Activity. The window must support Window.FEATURE_ACTIVITY_TRANSITIONS.
+     * The exit transition will start immediately and the shared element transition will
+     * start once the launched Activity's shared element is ready.
+     * <p>
+     * When all transitions have completed and the shared element has been transfered,
+     * the window's decor View will have its visibility set to View.GONE.
+     *
+     * @hide
+     */
+    @SafeVarargs
+    public static ActivityOptions startSharedElementAnimation(Window window,
+            Pair<View, String>... sharedElements) {
+        ActivityOptions opts = new ActivityOptions();
+        final View decorView = window.getDecorView();
+        if (decorView == null) {
+            return opts;
+        }
+        final ExitTransitionCoordinator exit =
+                makeSceneTransitionAnimation(null, window, opts, null, sharedElements);
+        if (exit != null) {
+            HideWindowListener listener = new HideWindowListener(window, exit);
+            exit.setHideSharedElementsCallback(listener);
+            exit.startExit();
+        }
+        return opts;
+    }
+
+    /**
+     * This method should be called when the {@link #startSharedElementAnimation(Window, Pair[])}
+     * animation must be stopped and the Views reset. This can happen if there was an error
+     * from startActivity or a springboard activity and the animation should stop and reset.
+     *
+     * @hide
+     */
+    public static void stopSharedElementAnimation(Window window) {
+        final View decorView = window.getDecorView();
+        if (decorView == null) {
+            return;
+        }
+        final ExitTransitionCoordinator exit = (ExitTransitionCoordinator)
+                decorView.getTag(com.android.internal.R.id.cross_task_transition);
+        if (exit != null) {
+            exit.cancelPendingTransitions();
+            decorView.setTagInternal(com.android.internal.R.id.cross_task_transition, null);
+            TransitionManager.endTransitions((ViewGroup) decorView);
+            exit.resetViews();
+            exit.clearState();
+            decorView.setVisibility(View.VISIBLE);
+        }
+    }
+
+    static ExitTransitionCoordinator makeSceneTransitionAnimation(Activity activity, Window window,
+            ActivityOptions opts, SharedElementCallback callback,
+            Pair<View, String>[] sharedElements) {
+        if (!window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)) {
+            opts.mAnimationType = ANIM_DEFAULT;
+            return null;
+        }
+        opts.mAnimationType = ANIM_SCENE_TRANSITION;
+
+        ArrayList<String> names = new ArrayList<String>();
+        ArrayList<View> views = new ArrayList<View>();
+
+        if (sharedElements != null) {
+            for (int i = 0; i < sharedElements.length; i++) {
+                Pair<View, String> sharedElement = sharedElements[i];
+                String sharedElementName = sharedElement.second;
+                if (sharedElementName == null) {
+                    throw new IllegalArgumentException("Shared element name must not be null");
+                }
+                names.add(sharedElementName);
+                View view = sharedElement.first;
+                if (view == null) {
+                    throw new IllegalArgumentException("Shared element must not be null");
+                }
+                views.add(sharedElement.first);
+            }
+        }
+
+        ExitTransitionCoordinator exit = new ExitTransitionCoordinator(activity, window,
+                callback, names, names, views, false);
+        opts.mTransitionReceiver = exit;
+        opts.mSharedElementNames = names;
+        opts.mIsReturning = (activity == null);
+        if (activity == null) {
+            opts.mExitCoordinatorIndex = -1;
+        } else {
+            opts.mExitCoordinatorIndex =
+                    activity.mActivityTransitionState.addExitTransitionCoordinator(exit);
+        }
+        return exit;
+    }
+
+    /** @hide */
+    static ActivityOptions makeSceneTransitionAnimation(Activity activity,
+            ExitTransitionCoordinator exitCoordinator, ArrayList<String> sharedElementNames,
+            int resultCode, Intent resultData) {
+        ActivityOptions opts = new ActivityOptions();
+        opts.mAnimationType = ANIM_SCENE_TRANSITION;
+        opts.mSharedElementNames = sharedElementNames;
+        opts.mTransitionReceiver = exitCoordinator;
+        opts.mIsReturning = true;
+        opts.mResultCode = resultCode;
+        opts.mResultData = resultData;
+        opts.mExitCoordinatorIndex =
+                activity.mActivityTransitionState.addExitTransitionCoordinator(exitCoordinator);
+        return opts;
+    }
+
+    /**
+     * If set along with Intent.FLAG_ACTIVITY_NEW_DOCUMENT then the task being launched will not be
+     * presented to the user but will instead be only available through the recents task list.
+     * In addition, the new task wil be affiliated with the launching activity's task.
+     * Affiliated tasks are grouped together in the recents task list.
+     *
+     * <p>This behavior is not supported for activities with {@link
+     * android.R.styleable#AndroidManifestActivity_launchMode launchMode} values of
+     * <code>singleInstance</code> or <code>singleTask</code>.
+     */
+    public static ActivityOptions makeTaskLaunchBehind() {
+        final ActivityOptions opts = new ActivityOptions();
+        opts.mAnimationType = ANIM_LAUNCH_TASK_BEHIND;
+        return opts;
+    }
+
+    /**
+     * Create a basic ActivityOptions that has no special animation associated with it.
+     * Other options can still be set.
+     */
+    public static ActivityOptions makeBasic() {
+        final ActivityOptions opts = new ActivityOptions();
+        return opts;
+    }
+
+    /**
+     * Create an {@link ActivityOptions} instance that lets the application control the entire
+     * animation using a {@link RemoteAnimationAdapter}.
+     * @hide
+     */
+    @RequiresPermission(CONTROL_REMOTE_APP_TRANSITION_ANIMATIONS)
+    @UnsupportedAppUsage
+    public static ActivityOptions makeRemoteAnimation(
+            RemoteAnimationAdapter remoteAnimationAdapter) {
+        final ActivityOptions opts = new ActivityOptions();
+        opts.mRemoteAnimationAdapter = remoteAnimationAdapter;
+        opts.mAnimationType = ANIM_REMOTE_ANIMATION;
+        return opts;
+    }
+
+    /** @hide */
+    public boolean getLaunchTaskBehind() {
+        return mAnimationType == ANIM_LAUNCH_TASK_BEHIND;
+    }
+
+    private ActivityOptions() {
+    }
+
+    /** @hide */
+    public ActivityOptions(Bundle opts) {
+        // If the remote side sent us bad parcelables, they won't get the
+        // results they want, which is their loss.
+        opts.setDefusable(true);
+
+        mPackageName = opts.getString(KEY_PACKAGE_NAME);
+        try {
+            mUsageTimeReport = opts.getParcelable(KEY_USAGE_TIME_REPORT);
+        } catch (RuntimeException e) {
+            Slog.w(TAG, e);
+        }
+        mLaunchBounds = opts.getParcelable(KEY_LAUNCH_BOUNDS);
+        mAnimationType = opts.getInt(KEY_ANIM_TYPE);
+        switch (mAnimationType) {
+            case ANIM_CUSTOM:
+                mCustomEnterResId = opts.getInt(KEY_ANIM_ENTER_RES_ID, 0);
+                mCustomExitResId = opts.getInt(KEY_ANIM_EXIT_RES_ID, 0);
+                mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
+                        opts.getBinder(KEY_ANIM_START_LISTENER));
+                break;
+
+            case ANIM_CUSTOM_IN_PLACE:
+                mCustomInPlaceResId = opts.getInt(KEY_ANIM_IN_PLACE_RES_ID, 0);
+                break;
+
+            case ANIM_SCALE_UP:
+            case ANIM_CLIP_REVEAL:
+                mStartX = opts.getInt(KEY_ANIM_START_X, 0);
+                mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
+                mWidth = opts.getInt(KEY_ANIM_WIDTH, 0);
+                mHeight = opts.getInt(KEY_ANIM_HEIGHT, 0);
+                break;
+
+            case ANIM_THUMBNAIL_SCALE_UP:
+            case ANIM_THUMBNAIL_SCALE_DOWN:
+            case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
+            case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
+                // Unpackage the GraphicBuffer from the parceled thumbnail
+                final GraphicBuffer buffer = opts.getParcelable(KEY_ANIM_THUMBNAIL);
+                if (buffer != null) {
+                    mThumbnail = Bitmap.wrapHardwareBuffer(buffer, null);
+                }
+                mStartX = opts.getInt(KEY_ANIM_START_X, 0);
+                mStartY = opts.getInt(KEY_ANIM_START_Y, 0);
+                mWidth = opts.getInt(KEY_ANIM_WIDTH, 0);
+                mHeight = opts.getInt(KEY_ANIM_HEIGHT, 0);
+                mAnimationStartedListener = IRemoteCallback.Stub.asInterface(
+                        opts.getBinder(KEY_ANIM_START_LISTENER));
+                break;
+
+            case ANIM_SCENE_TRANSITION:
+                mTransitionReceiver = opts.getParcelable(KEY_TRANSITION_COMPLETE_LISTENER);
+                mIsReturning = opts.getBoolean(KEY_TRANSITION_IS_RETURNING, false);
+                mSharedElementNames = opts.getStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS);
+                mResultData = opts.getParcelable(KEY_RESULT_DATA);
+                mResultCode = opts.getInt(KEY_RESULT_CODE);
+                mExitCoordinatorIndex = opts.getInt(KEY_EXIT_COORDINATOR_INDEX);
+                break;
+        }
+        mLockTaskMode = opts.getBoolean(KEY_LOCK_TASK_MODE, false);
+        mLaunchDisplayId = opts.getInt(KEY_LAUNCH_DISPLAY_ID, INVALID_DISPLAY);
+        mLaunchWindowingMode = opts.getInt(KEY_LAUNCH_WINDOWING_MODE, WINDOWING_MODE_UNDEFINED);
+        mLaunchActivityType = opts.getInt(KEY_LAUNCH_ACTIVITY_TYPE, ACTIVITY_TYPE_UNDEFINED);
+        mLaunchTaskId = opts.getInt(KEY_LAUNCH_TASK_ID, -1);
+        mPendingIntentLaunchFlags = opts.getInt(KEY_PENDING_INTENT_LAUNCH_FLAGS, 0);
+        mTaskOverlay = opts.getBoolean(KEY_TASK_OVERLAY, false);
+        mTaskOverlayCanResume = opts.getBoolean(KEY_TASK_OVERLAY_CAN_RESUME, false);
+        mAvoidMoveToFront = opts.getBoolean(KEY_AVOID_MOVE_TO_FRONT, false);
+        mFreezeRecentTasksReordering = opts.getBoolean(KEY_FREEZE_RECENT_TASKS_REORDERING, false);
+        mSplitScreenCreateMode = opts.getInt(KEY_SPLIT_SCREEN_CREATE_MODE,
+                SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT);
+        mDisallowEnterPictureInPictureWhileLaunching = opts.getBoolean(
+                KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING, false);
+        if (opts.containsKey(KEY_ANIM_SPECS)) {
+            Parcelable[] specs = opts.getParcelableArray(KEY_ANIM_SPECS);
+            mAnimSpecs = new AppTransitionAnimationSpec[specs.length];
+            for (int i = specs.length - 1; i >= 0; i--) {
+                mAnimSpecs[i] = (AppTransitionAnimationSpec) specs[i];
+            }
+        }
+        if (opts.containsKey(KEY_ANIMATION_FINISHED_LISTENER)) {
+            mAnimationFinishedListener = IRemoteCallback.Stub.asInterface(
+                    opts.getBinder(KEY_ANIMATION_FINISHED_LISTENER));
+        }
+        mRotationAnimationHint = opts.getInt(KEY_ROTATION_ANIMATION_HINT, -1);
+        mAppVerificationBundle = opts.getBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE);
+        if (opts.containsKey(KEY_SPECS_FUTURE)) {
+            mSpecsFuture = IAppTransitionAnimationSpecsFuture.Stub.asInterface(opts.getBinder(
+                    KEY_SPECS_FUTURE));
+        }
+        mRemoteAnimationAdapter = opts.getParcelable(KEY_REMOTE_ANIMATION_ADAPTER);
+    }
+
+    /**
+     * Sets the bounds (window size and position) that the activity should be launched in.
+     * Rect position should be provided in pixels and in screen coordinates.
+     * Set to {@code null} to explicitly launch fullscreen.
+     * <p>
+     * <strong>NOTE:</strong> This value is ignored on devices that don't have
+     * {@link android.content.pm.PackageManager#FEATURE_FREEFORM_WINDOW_MANAGEMENT} or
+     * {@link android.content.pm.PackageManager#FEATURE_PICTURE_IN_PICTURE} enabled.
+     * @param screenSpacePixelRect launch bounds or {@code null} for fullscreen
+     * @return {@code this} {@link ActivityOptions} instance
+     */
+    public ActivityOptions setLaunchBounds(@Nullable Rect screenSpacePixelRect) {
+        mLaunchBounds = screenSpacePixelRect != null ? new Rect(screenSpacePixelRect) : null;
+        return this;
+    }
+
+    /** @hide */
+    public String getPackageName() {
+        return mPackageName;
+    }
+
+    /**
+     * Returns the bounds that should be used to launch the activity.
+     * @see #setLaunchBounds(Rect)
+     * @return Bounds used to launch the activity.
+     */
+    @Nullable
+    public Rect getLaunchBounds() {
+        return mLaunchBounds;
+    }
+
+    /** @hide */
+    public int getAnimationType() {
+        return mAnimationType;
+    }
+
+    /** @hide */
+    public int getCustomEnterResId() {
+        return mCustomEnterResId;
+    }
+
+    /** @hide */
+    public int getCustomExitResId() {
+        return mCustomExitResId;
+    }
+
+    /** @hide */
+    public int getCustomInPlaceResId() {
+        return mCustomInPlaceResId;
+    }
+
+    /**
+     * The thumbnail is copied into a hardware bitmap when it is bundled and sent to the system, so
+     * it should always be backed by a GraphicBuffer on the other end.
+     *
+     * @hide
+     */
+    public GraphicBuffer getThumbnail() {
+        return mThumbnail != null ? mThumbnail.createGraphicBufferHandle() : null;
+    }
+
+    /** @hide */
+    public int getStartX() {
+        return mStartX;
+    }
+
+    /** @hide */
+    public int getStartY() {
+        return mStartY;
+    }
+
+    /** @hide */
+    public int getWidth() {
+        return mWidth;
+    }
+
+    /** @hide */
+    public int getHeight() {
+        return mHeight;
+    }
+
+    /** @hide */
+    public IRemoteCallback getOnAnimationStartListener() {
+        return mAnimationStartedListener;
+    }
+
+    /** @hide */
+    public IRemoteCallback getAnimationFinishedListener() {
+        return mAnimationFinishedListener;
+    }
+
+    /** @hide */
+    public int getExitCoordinatorKey() { return mExitCoordinatorIndex; }
+
+    /** @hide */
+    public void abort() {
+        if (mAnimationStartedListener != null) {
+            try {
+                mAnimationStartedListener.sendResult(null);
+            } catch (RemoteException e) {
+            }
+        }
+    }
+
+    /** @hide */
+    public boolean isReturning() {
+        return mIsReturning;
+    }
+
+    /**
+     * Returns whether or not the ActivityOptions was created with
+     * {@link #startSharedElementAnimation(Window, Pair[])}.
+     *
+     * @hide
+     */
+    boolean isCrossTask() {
+        return mExitCoordinatorIndex < 0;
+    }
+
+    /** @hide */
+    public ArrayList<String> getSharedElementNames() {
+        return mSharedElementNames;
+    }
+
+    /** @hide */
+    public ResultReceiver getResultReceiver() { return mTransitionReceiver; }
+
+    /** @hide */
+    public int getResultCode() { return mResultCode; }
+
+    /** @hide */
+    public Intent getResultData() { return mResultData; }
+
+    /** @hide */
+    public PendingIntent getUsageTimeReport() {
+        return mUsageTimeReport;
+    }
+
+    /** @hide */
+    public AppTransitionAnimationSpec[] getAnimSpecs() { return mAnimSpecs; }
+
+    /** @hide */
+    public IAppTransitionAnimationSpecsFuture getSpecsFuture() {
+        return mSpecsFuture;
+    }
+
+    /** @hide */
+    public RemoteAnimationAdapter getRemoteAnimationAdapter() {
+        return mRemoteAnimationAdapter;
+    }
+
+    /** @hide */
+    public void setRemoteAnimationAdapter(RemoteAnimationAdapter remoteAnimationAdapter) {
+        mRemoteAnimationAdapter = remoteAnimationAdapter;
+    }
+
+    /** @hide */
+    public static ActivityOptions fromBundle(Bundle bOptions) {
+        return bOptions != null ? new ActivityOptions(bOptions) : null;
+    }
+
+    /** @hide */
+    public static void abort(ActivityOptions options) {
+        if (options != null) {
+            options.abort();
+        }
+    }
+
+    /**
+     * Gets whether the activity is to be launched into LockTask mode.
+     * @return {@code true} if the activity is to be launched into LockTask mode.
+     * @see Activity#startLockTask()
+     * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+     */
+    public boolean getLockTaskMode() {
+        return mLockTaskMode;
+    }
+
+    /**
+     * Sets whether the activity is to be launched into LockTask mode.
+     *
+     * Use this option to start an activity in LockTask mode. Note that only apps permitted by
+     * {@link android.app.admin.DevicePolicyManager} can run in LockTask mode. Therefore, if
+     * {@link android.app.admin.DevicePolicyManager#isLockTaskPermitted(String)} returns
+     * {@code false} for the package of the target activity, a {@link SecurityException} will be
+     * thrown during {@link Context#startActivity(Intent, Bundle)}. This method doesn't affect
+     * activities that are already running — relaunch the activity to run in lock task mode.
+     *
+     * Defaults to {@code false} if not set.
+     *
+     * @param lockTaskMode {@code true} if the activity is to be launched into LockTask mode.
+     * @return {@code this} {@link ActivityOptions} instance.
+     * @see Activity#startLockTask()
+     * @see android.app.admin.DevicePolicyManager#setLockTaskPackages(ComponentName, String[])
+     */
+    public ActivityOptions setLockTaskEnabled(boolean lockTaskMode) {
+        mLockTaskMode = lockTaskMode;
+        return this;
+    }
+
+    /**
+     * Gets the id of the display where activity should be launched.
+     * @return The id of the display where activity should be launched,
+     *         {@link android.view.Display#INVALID_DISPLAY} if not set.
+     * @see #setLaunchDisplayId(int)
+     */
+    public int getLaunchDisplayId() {
+        return mLaunchDisplayId;
+    }
+
+    /**
+     * Sets the id of the display where activity should be launched.
+     * An app can launch activities on public displays or private displays that are owned by the app
+     * or where an app already has activities. Otherwise, trying to launch on a private display
+     * or providing an invalid display id will result in an exception.
+     * <p>
+     * Setting launch display id will be ignored on devices that don't have
+     * {@link android.content.pm.PackageManager#FEATURE_ACTIVITIES_ON_SECONDARY_DISPLAYS}.
+     * @param launchDisplayId The id of the display where the activity should be launched.
+     * @return {@code this} {@link ActivityOptions} instance.
+     */
+    public ActivityOptions setLaunchDisplayId(int launchDisplayId) {
+        mLaunchDisplayId = launchDisplayId;
+        return this;
+    }
+
+    /** @hide */
+    public int getLaunchWindowingMode() {
+        return mLaunchWindowingMode;
+    }
+
+    /**
+     * Sets the windowing mode the activity should launch into. If the input windowing mode is
+     * {@link android.app.WindowConfiguration#WINDOWING_MODE_SPLIT_SCREEN_SECONDARY} and the device
+     * isn't currently in split-screen windowing mode, then the activity will be launched in
+     * {@link android.app.WindowConfiguration#WINDOWING_MODE_FULLSCREEN} windowing mode. For clarity
+     * on this you can use
+     * {@link android.app.WindowConfiguration#WINDOWING_MODE_FULLSCREEN_OR_SPLIT_SCREEN_SECONDARY}
+     *
+     * @hide
+     */
+    @TestApi
+    public void setLaunchWindowingMode(int windowingMode) {
+        mLaunchWindowingMode = windowingMode;
+    }
+
+    /** @hide */
+    public int getLaunchActivityType() {
+        return mLaunchActivityType;
+    }
+
+    /** @hide */
+    @TestApi
+    public void setLaunchActivityType(int activityType) {
+        mLaunchActivityType = activityType;
+    }
+
+    /**
+     * Sets the task the activity will be launched in.
+     * @hide
+     */
+    @TestApi
+    public void setLaunchTaskId(int taskId) {
+        mLaunchTaskId = taskId;
+    }
+
+    /**
+     * @hide
+     */
+    public int getLaunchTaskId() {
+        return mLaunchTaskId;
+    }
+
+    /**
+     * Specifies intent flags to be applied for any activity started from a PendingIntent.
+     *
+     * @hide
+     */
+    public void setPendingIntentLaunchFlags(@android.content.Intent.Flags int flags) {
+        mPendingIntentLaunchFlags = flags;
+    }
+
+    /**
+     * @hide
+     */
+    public int getPendingIntentLaunchFlags() {
+        return mPendingIntentLaunchFlags;
+    }
+
+    /**
+     * Set's whether the activity launched with this option should be a task overlay. That is the
+     * activity will always be the top activity of the task.  If {@param canResume} is true, then
+     * the task will also not be moved to the front of the stack.
+     * @hide
+     */
+    @TestApi
+    public void setTaskOverlay(boolean taskOverlay, boolean canResume) {
+        mTaskOverlay = taskOverlay;
+        mTaskOverlayCanResume = canResume;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean getTaskOverlay() {
+        return mTaskOverlay;
+    }
+
+    /**
+     * @hide
+     */
+    public boolean canTaskOverlayResume() {
+        return mTaskOverlayCanResume;
+    }
+
+    /**
+     * Sets whether the activity launched should not cause the activity stack it is contained in to
+     * be moved to the front as a part of launching.
+     *
+     * @hide
+     */
+    public void setAvoidMoveToFront() {
+        mAvoidMoveToFront = true;
+    }
+
+    /**
+     * @return whether the activity launch should prevent moving the associated activity stack to
+     *         the front.
+     * @hide
+     */
+    public boolean getAvoidMoveToFront() {
+        return mAvoidMoveToFront;
+    }
+
+    /**
+     * Sets whether the launch of this activity should freeze the recent task list reordering until
+     * the next user interaction or timeout. This flag is only applied when starting an activity
+     * in recents.
+     * @hide
+     */
+    public void setFreezeRecentTasksReordering() {
+        mFreezeRecentTasksReordering = true;
+    }
+
+    /**
+     * @return whether the launch of this activity should freeze the recent task list reordering
+     * @hide
+     */
+    public boolean freezeRecentTasksReordering() {
+        return mFreezeRecentTasksReordering;
+    }
+
+    /** @hide */
+    public int getSplitScreenCreateMode() {
+        return mSplitScreenCreateMode;
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public void setSplitScreenCreateMode(int splitScreenCreateMode) {
+        mSplitScreenCreateMode = splitScreenCreateMode;
+    }
+
+    /** @hide */
+    public void setDisallowEnterPictureInPictureWhileLaunching(boolean disallow) {
+        mDisallowEnterPictureInPictureWhileLaunching = disallow;
+    }
+
+    /** @hide */
+    public boolean disallowEnterPictureInPictureWhileLaunching() {
+        return mDisallowEnterPictureInPictureWhileLaunching;
+    }
+
+    /**
+     * Update the current values in this ActivityOptions from those supplied
+     * in <var>otherOptions</var>.  Any values
+     * defined in <var>otherOptions</var> replace those in the base options.
+     */
+    public void update(ActivityOptions otherOptions) {
+        if (otherOptions.mPackageName != null) {
+            mPackageName = otherOptions.mPackageName;
+        }
+        mUsageTimeReport = otherOptions.mUsageTimeReport;
+        mTransitionReceiver = null;
+        mSharedElementNames = null;
+        mIsReturning = false;
+        mResultData = null;
+        mResultCode = 0;
+        mExitCoordinatorIndex = 0;
+        mAnimationType = otherOptions.mAnimationType;
+        switch (otherOptions.mAnimationType) {
+            case ANIM_CUSTOM:
+                mCustomEnterResId = otherOptions.mCustomEnterResId;
+                mCustomExitResId = otherOptions.mCustomExitResId;
+                mThumbnail = null;
+                if (mAnimationStartedListener != null) {
+                    try {
+                        mAnimationStartedListener.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                }
+                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
+                break;
+            case ANIM_CUSTOM_IN_PLACE:
+                mCustomInPlaceResId = otherOptions.mCustomInPlaceResId;
+                break;
+            case ANIM_SCALE_UP:
+                mStartX = otherOptions.mStartX;
+                mStartY = otherOptions.mStartY;
+                mWidth = otherOptions.mWidth;
+                mHeight = otherOptions.mHeight;
+                if (mAnimationStartedListener != null) {
+                    try {
+                        mAnimationStartedListener.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                }
+                mAnimationStartedListener = null;
+                break;
+            case ANIM_THUMBNAIL_SCALE_UP:
+            case ANIM_THUMBNAIL_SCALE_DOWN:
+            case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
+            case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
+                mThumbnail = otherOptions.mThumbnail;
+                mStartX = otherOptions.mStartX;
+                mStartY = otherOptions.mStartY;
+                mWidth = otherOptions.mWidth;
+                mHeight = otherOptions.mHeight;
+                if (mAnimationStartedListener != null) {
+                    try {
+                        mAnimationStartedListener.sendResult(null);
+                    } catch (RemoteException e) {
+                    }
+                }
+                mAnimationStartedListener = otherOptions.mAnimationStartedListener;
+                break;
+            case ANIM_SCENE_TRANSITION:
+                mTransitionReceiver = otherOptions.mTransitionReceiver;
+                mSharedElementNames = otherOptions.mSharedElementNames;
+                mIsReturning = otherOptions.mIsReturning;
+                mThumbnail = null;
+                mAnimationStartedListener = null;
+                mResultData = otherOptions.mResultData;
+                mResultCode = otherOptions.mResultCode;
+                mExitCoordinatorIndex = otherOptions.mExitCoordinatorIndex;
+                break;
+        }
+        mLockTaskMode = otherOptions.mLockTaskMode;
+        mAnimSpecs = otherOptions.mAnimSpecs;
+        mAnimationFinishedListener = otherOptions.mAnimationFinishedListener;
+        mSpecsFuture = otherOptions.mSpecsFuture;
+        mRemoteAnimationAdapter = otherOptions.mRemoteAnimationAdapter;
+    }
+
+    /**
+     * Returns the created options as a Bundle, which can be passed to
+     * {@link android.content.Context#startActivity(android.content.Intent, android.os.Bundle)
+     * Context.startActivity(Intent, Bundle)} and related methods.
+     * Note that the returned Bundle is still owned by the ActivityOptions
+     * object; you must not modify it, but can supply it to the startActivity
+     * methods that take an options Bundle.
+     */
+    public Bundle toBundle() {
+        Bundle b = new Bundle();
+        if (mPackageName != null) {
+            b.putString(KEY_PACKAGE_NAME, mPackageName);
+        }
+        if (mLaunchBounds != null) {
+            b.putParcelable(KEY_LAUNCH_BOUNDS, mLaunchBounds);
+        }
+        b.putInt(KEY_ANIM_TYPE, mAnimationType);
+        if (mUsageTimeReport != null) {
+            b.putParcelable(KEY_USAGE_TIME_REPORT, mUsageTimeReport);
+        }
+        switch (mAnimationType) {
+            case ANIM_CUSTOM:
+                b.putInt(KEY_ANIM_ENTER_RES_ID, mCustomEnterResId);
+                b.putInt(KEY_ANIM_EXIT_RES_ID, mCustomExitResId);
+                b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+                        != null ? mAnimationStartedListener.asBinder() : null);
+                break;
+            case ANIM_CUSTOM_IN_PLACE:
+                b.putInt(KEY_ANIM_IN_PLACE_RES_ID, mCustomInPlaceResId);
+                break;
+            case ANIM_SCALE_UP:
+            case ANIM_CLIP_REVEAL:
+                b.putInt(KEY_ANIM_START_X, mStartX);
+                b.putInt(KEY_ANIM_START_Y, mStartY);
+                b.putInt(KEY_ANIM_WIDTH, mWidth);
+                b.putInt(KEY_ANIM_HEIGHT, mHeight);
+                break;
+            case ANIM_THUMBNAIL_SCALE_UP:
+            case ANIM_THUMBNAIL_SCALE_DOWN:
+            case ANIM_THUMBNAIL_ASPECT_SCALE_UP:
+            case ANIM_THUMBNAIL_ASPECT_SCALE_DOWN:
+                // Once we parcel the thumbnail for transfering over to the system, create a copy of
+                // the bitmap to a hardware bitmap and pass through the GraphicBuffer
+                if (mThumbnail != null) {
+                    final Bitmap hwBitmap = mThumbnail.copy(Config.HARDWARE, false /* isMutable */);
+                    if (hwBitmap != null) {
+                        b.putParcelable(KEY_ANIM_THUMBNAIL, hwBitmap.createGraphicBufferHandle());
+                    } else {
+                        Slog.w(TAG, "Failed to copy thumbnail");
+                    }
+                }
+                b.putInt(KEY_ANIM_START_X, mStartX);
+                b.putInt(KEY_ANIM_START_Y, mStartY);
+                b.putInt(KEY_ANIM_WIDTH, mWidth);
+                b.putInt(KEY_ANIM_HEIGHT, mHeight);
+                b.putBinder(KEY_ANIM_START_LISTENER, mAnimationStartedListener
+                        != null ? mAnimationStartedListener.asBinder() : null);
+                break;
+            case ANIM_SCENE_TRANSITION:
+                if (mTransitionReceiver != null) {
+                    b.putParcelable(KEY_TRANSITION_COMPLETE_LISTENER, mTransitionReceiver);
+                }
+                b.putBoolean(KEY_TRANSITION_IS_RETURNING, mIsReturning);
+                b.putStringArrayList(KEY_TRANSITION_SHARED_ELEMENTS, mSharedElementNames);
+                b.putParcelable(KEY_RESULT_DATA, mResultData);
+                b.putInt(KEY_RESULT_CODE, mResultCode);
+                b.putInt(KEY_EXIT_COORDINATOR_INDEX, mExitCoordinatorIndex);
+                break;
+        }
+        if (mLockTaskMode) {
+            b.putBoolean(KEY_LOCK_TASK_MODE, mLockTaskMode);
+        }
+        if (mLaunchDisplayId != INVALID_DISPLAY) {
+            b.putInt(KEY_LAUNCH_DISPLAY_ID, mLaunchDisplayId);
+        }
+        if (mLaunchWindowingMode != WINDOWING_MODE_UNDEFINED) {
+            b.putInt(KEY_LAUNCH_WINDOWING_MODE, mLaunchWindowingMode);
+        }
+        if (mLaunchActivityType != ACTIVITY_TYPE_UNDEFINED) {
+            b.putInt(KEY_LAUNCH_ACTIVITY_TYPE, mLaunchActivityType);
+        }
+        if (mLaunchTaskId != -1) {
+            b.putInt(KEY_LAUNCH_TASK_ID, mLaunchTaskId);
+        }
+        if (mPendingIntentLaunchFlags != 0) {
+            b.putInt(KEY_PENDING_INTENT_LAUNCH_FLAGS, mPendingIntentLaunchFlags);
+        }
+        if (mTaskOverlay) {
+            b.putBoolean(KEY_TASK_OVERLAY, mTaskOverlay);
+        }
+        if (mTaskOverlayCanResume) {
+            b.putBoolean(KEY_TASK_OVERLAY_CAN_RESUME, mTaskOverlayCanResume);
+        }
+        if (mAvoidMoveToFront) {
+            b.putBoolean(KEY_AVOID_MOVE_TO_FRONT, mAvoidMoveToFront);
+        }
+        if (mFreezeRecentTasksReordering) {
+            b.putBoolean(KEY_FREEZE_RECENT_TASKS_REORDERING, mFreezeRecentTasksReordering);
+        }
+        if (mSplitScreenCreateMode != SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT) {
+            b.putInt(KEY_SPLIT_SCREEN_CREATE_MODE, mSplitScreenCreateMode);
+        }
+        if (mDisallowEnterPictureInPictureWhileLaunching) {
+            b.putBoolean(KEY_DISALLOW_ENTER_PICTURE_IN_PICTURE_WHILE_LAUNCHING,
+                    mDisallowEnterPictureInPictureWhileLaunching);
+        }
+        if (mAnimSpecs != null) {
+            b.putParcelableArray(KEY_ANIM_SPECS, mAnimSpecs);
+        }
+        if (mAnimationFinishedListener != null) {
+            b.putBinder(KEY_ANIMATION_FINISHED_LISTENER, mAnimationFinishedListener.asBinder());
+        }
+        if (mSpecsFuture != null) {
+            b.putBinder(KEY_SPECS_FUTURE, mSpecsFuture.asBinder());
+        }
+        if (mRotationAnimationHint != -1) {
+            b.putInt(KEY_ROTATION_ANIMATION_HINT, mRotationAnimationHint);
+        }
+        if (mAppVerificationBundle != null) {
+            b.putBundle(KEY_INSTANT_APP_VERIFICATION_BUNDLE, mAppVerificationBundle);
+        }
+        if (mRemoteAnimationAdapter != null) {
+            b.putParcelable(KEY_REMOTE_ANIMATION_ADAPTER, mRemoteAnimationAdapter);
+        }
+        return b;
+    }
+
+    /**
+     * Ask the system track that time the user spends in the app being launched, and
+     * report it back once done.  The report will be sent to the given receiver, with
+     * the extras {@link #EXTRA_USAGE_TIME_REPORT} and {@link #EXTRA_USAGE_TIME_REPORT_PACKAGES}
+     * filled in.
+     *
+     * <p>The time interval tracked is from launching this activity until the user leaves
+     * that activity's flow.  They are considered to stay in the flow as long as
+     * new activities are being launched or returned to from the original flow,
+     * even if this crosses package or task boundaries.  For example, if the originator
+     * starts an activity to view an image, and while there the user selects to share,
+     * which launches their email app in a new task, and they complete the share, the
+     * time during that entire operation will be included until they finally hit back from
+     * the original image viewer activity.</p>
+     *
+     * <p>The user is considered to complete a flow once they switch to another
+     * activity that is not part of the tracked flow.  This may happen, for example, by
+     * using the notification shade, launcher, or recents to launch or switch to another
+     * app.  Simply going in to these navigation elements does not break the flow (although
+     * the launcher and recents stops time tracking of the session); it is the act of
+     * going somewhere else that completes the tracking.</p>
+     *
+     * @param receiver A broadcast receiver that willl receive the report.
+     */
+    public void requestUsageTimeReport(PendingIntent receiver) {
+        mUsageTimeReport = receiver;
+    }
+
+    /**
+     * Return the filtered options only meant to be seen by the target activity itself
+     * @hide
+     */
+    public ActivityOptions forTargetActivity() {
+        if (mAnimationType == ANIM_SCENE_TRANSITION) {
+            final ActivityOptions result = new ActivityOptions();
+            result.update(this);
+            return result;
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the rotation animation set by {@link setRotationAnimationHint} or -1
+     * if unspecified.
+     * @hide
+     */
+    public int getRotationAnimationHint() {
+        return mRotationAnimationHint;
+    }
+
+
+    /**
+     * Set a rotation animation to be used if launching the activity
+     * triggers an orientation change, or -1 to clear. See
+     * {@link android.view.WindowManager.LayoutParams} for rotation
+     * animation values.
+     * @hide
+     */
+    public void setRotationAnimationHint(int hint) {
+        mRotationAnimationHint = hint;
+    }
+
+    /**
+     * Pop the extra verification bundle for the installer.
+     * This removes the bundle from the ActivityOptions to make sure the installer bundle
+     * is only available once.
+     * @hide
+     */
+    public Bundle popAppVerificationBundle() {
+        Bundle out = mAppVerificationBundle;
+        mAppVerificationBundle = null;
+        return out;
+    }
+
+    /**
+     * Set the {@link Bundle} that is provided to the app installer for additional verification
+     * if the call to {@link Context#startActivity} results in an app being installed.
+     *
+     * This Bundle is not provided to any other app besides the installer.
+     */
+    public ActivityOptions setAppVerificationBundle(Bundle bundle) {
+        mAppVerificationBundle = bundle;
+        return this;
+
+    }
+
+    /** @hide */
+    @Override
+    public String toString() {
+        return "ActivityOptions(" + hashCode() + "), mPackageName=" + mPackageName
+                + ", mAnimationType=" + mAnimationType + ", mStartX=" + mStartX + ", mStartY="
+                + mStartY + ", mWidth=" + mWidth + ", mHeight=" + mHeight;
+    }
+
+    private static class HideWindowListener extends TransitionListenerAdapter
+        implements ExitTransitionCoordinator.HideSharedElementsCallback {
+        private final Window mWindow;
+        private final ExitTransitionCoordinator mExit;
+        private final boolean mWaitingForTransition;
+        private boolean mTransitionEnded;
+        private boolean mSharedElementHidden;
+        private ArrayList<View> mSharedElements;
+
+        public HideWindowListener(Window window, ExitTransitionCoordinator exit) {
+            mWindow = window;
+            mExit = exit;
+            mSharedElements = new ArrayList<>(exit.mSharedElements);
+            Transition transition = mWindow.getExitTransition();
+            if (transition != null) {
+                transition.addListener(this);
+                mWaitingForTransition = true;
+            } else {
+                mWaitingForTransition = false;
+            }
+            View decorView = mWindow.getDecorView();
+            if (decorView != null) {
+                if (decorView.getTag(com.android.internal.R.id.cross_task_transition) != null) {
+                    throw new IllegalStateException(
+                            "Cannot start a transition while one is running");
+                }
+                decorView.setTagInternal(com.android.internal.R.id.cross_task_transition, exit);
+            }
+        }
+
+        @Override
+        public void onTransitionEnd(Transition transition) {
+            mTransitionEnded = true;
+            hideWhenDone();
+            transition.removeListener(this);
+        }
+
+        @Override
+        public void hideSharedElements() {
+            mSharedElementHidden = true;
+            hideWhenDone();
+        }
+
+        private void hideWhenDone() {
+            if (mSharedElementHidden && (!mWaitingForTransition || mTransitionEnded)) {
+                mExit.resetViews();
+                int numSharedElements = mSharedElements.size();
+                for (int i = 0; i < numSharedElements; i++) {
+                    View view = mSharedElements.get(i);
+                    view.requestLayout();
+                }
+                View decorView = mWindow.getDecorView();
+                if (decorView != null) {
+                    decorView.setTagInternal(
+                            com.android.internal.R.id.cross_task_transition, null);
+                    decorView.setVisibility(View.GONE);
+                }
+            }
+        }
+    }
+}
diff --git a/android/app/ActivityTaskManager.java b/android/app/ActivityTaskManager.java
new file mode 100644
index 0000000..4ef554d
--- /dev/null
+++ b/android/app/ActivityTaskManager.java
@@ -0,0 +1,448 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.RequiresPermission;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.Singleton;
+
+import java.util.List;
+
+/**
+ * This class gives information about, and interacts with activities and their containers like task,
+ * stacks, and displays.
+ *
+ * @hide
+ */
+@TestApi
+@SystemService(Context.ACTIVITY_TASK_SERVICE)
+public class ActivityTaskManager {
+
+    /** Invalid stack ID. */
+    public static final int INVALID_STACK_ID = -1;
+
+    /**
+     * Invalid task ID.
+     * @hide
+     */
+    public static final int INVALID_TASK_ID = -1;
+
+    /**
+     * Parameter to {@link IActivityTaskManager#setTaskWindowingModeSplitScreenPrimary} which
+     * specifies the position of the created docked stack at the top half of the screen if
+     * in portrait mode or at the left half of the screen if in landscape mode.
+     */
+    public static final int SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT = 0;
+
+    /**
+     * Parameter to {@link IActivityTaskManager#setTaskWindowingModeSplitScreenPrimary} which
+     * specifies the position of the created docked stack at the bottom half of the screen if
+     * in portrait mode or at the right half of the screen if in landscape mode.
+     */
+    public static final int SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT = 1;
+
+    /**
+     * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
+     * that the resize doesn't need to preserve the window, and can be skipped if bounds
+     * is unchanged. This mode is used by window manager in most cases.
+     * @hide
+     */
+    public static final int RESIZE_MODE_SYSTEM = 0;
+
+    /**
+     * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
+     * that the resize should preserve the window if possible.
+     * @hide
+     */
+    public static final int RESIZE_MODE_PRESERVE_WINDOW   = (0x1 << 0);
+
+    /**
+     * Input parameter to {@link IActivityTaskManager#resizeTask} used when the
+     * resize is due to a drag action.
+     * @hide
+     */
+    public static final int RESIZE_MODE_USER = RESIZE_MODE_PRESERVE_WINDOW;
+
+    /**
+     * Input parameter to {@link IActivityTaskManager#resizeTask} used by window
+     * manager during a screen rotation.
+     * @hide
+     */
+    public static final int RESIZE_MODE_SYSTEM_SCREEN_ROTATION = RESIZE_MODE_PRESERVE_WINDOW;
+
+    /**
+     * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
+     * that the resize should be performed even if the bounds appears unchanged.
+     * @hide
+     */
+    public static final int RESIZE_MODE_FORCED = (0x1 << 1);
+
+    /**
+     * Input parameter to {@link IActivityTaskManager#resizeTask} which indicates
+     * that the resize should preserve the window if possible, and should not be skipped
+     * even if the bounds is unchanged. Usually used to force a resizing when a drag action
+     * is ending.
+     * @hide
+     */
+    public static final int RESIZE_MODE_USER_FORCED =
+            RESIZE_MODE_PRESERVE_WINDOW | RESIZE_MODE_FORCED;
+
+    /**
+     * Extra included on intents that are delegating the call to
+     * ActivityManager#startActivityAsCaller to another app.  This token is necessary for that call
+     * to succeed.  Type is IBinder.
+     * @hide
+     */
+    public static final String EXTRA_PERMISSION_TOKEN = "android.app.extra.PERMISSION_TOKEN";
+
+    /**
+     * Extra included on intents that contain an EXTRA_INTENT, with options that the contained
+     * intent may want to be started with.  Type is Bundle.
+     * TODO: remove once the ChooserActivity moves to systemui
+     * @hide
+     */
+    public static final String EXTRA_OPTIONS = "android.app.extra.OPTIONS";
+
+    /**
+     * Extra included on intents that contain an EXTRA_INTENT, use this boolean value for the
+     * parameter of the same name when starting the contained intent.
+     * TODO: remove once the ChooserActivity moves to systemui
+     * @hide
+     */
+    public static final String EXTRA_IGNORE_TARGET_SECURITY =
+            "android.app.extra.EXTRA_IGNORE_TARGET_SECURITY";
+
+
+    private static int sMaxRecentTasks = -1;
+
+    ActivityTaskManager(Context context, Handler handler) {
+    }
+
+    /** @hide */
+    public static IActivityTaskManager getService() {
+        return IActivityTaskManagerSingleton.get();
+    }
+
+    @UnsupportedAppUsage(trackingBug = 129726065)
+    private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
+            new Singleton<IActivityTaskManager>() {
+                @Override
+                protected IActivityTaskManager create() {
+                    final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
+                    return IActivityTaskManager.Stub.asInterface(b);
+                }
+            };
+
+    /**
+     * Sets the windowing mode for a specific task. Only works on tasks of type
+     * {@link WindowConfiguration#ACTIVITY_TYPE_STANDARD}
+     * @param taskId The id of the task to set the windowing mode for.
+     * @param windowingMode The windowing mode to set for the task.
+     * @param toTop If the task should be moved to the top once the windowing mode changes.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void setTaskWindowingMode(int taskId, int windowingMode, boolean toTop)
+            throws SecurityException {
+        try {
+            getService().setTaskWindowingMode(taskId, windowingMode, toTop);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Moves the input task to the primary-split-screen stack.
+     * @param taskId Id of task to move.
+     * @param createMode The mode the primary split screen stack should be created in if it doesn't
+     *                   exist already. See
+     *                   {@link ActivityTaskManager#SPLIT_SCREEN_CREATE_MODE_TOP_OR_LEFT}
+     *                   and
+     *                   {@link android.app.ActivityManager
+     *                        #SPLIT_SCREEN_CREATE_MODE_BOTTOM_OR_RIGHT}
+     * @param toTop If the task and stack should be moved to the top.
+     * @param animate Whether we should play an animation for the moving the task
+     * @param initialBounds If the primary stack gets created, it will use these bounds for the
+     *                      docked stack. Pass {@code null} to use default bounds.
+     * @param showRecents If the recents activity should be shown on the other side of the task
+     *                    going into split-screen mode.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void setTaskWindowingModeSplitScreenPrimary(int taskId, int createMode, boolean toTop,
+            boolean animate, Rect initialBounds, boolean showRecents) throws SecurityException {
+        try {
+            getService().setTaskWindowingModeSplitScreenPrimary(taskId, createMode, toTop, animate,
+                    initialBounds, showRecents);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Resizes the input stack id to the given bounds.
+     * @param stackId Id of the stack to resize.
+     * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void resizeStack(int stackId, Rect bounds) throws SecurityException {
+        try {
+            getService().resizeStack(stackId, bounds, false /* allowResizeInDockedMode */,
+                    false /* preserveWindows */, false /* animate */, -1 /* animationDuration */);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes stacks in the windowing modes from the system if they are of activity type
+     * ACTIVITY_TYPE_STANDARD or ACTIVITY_TYPE_UNDEFINED
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void removeStacksInWindowingModes(int[] windowingModes) throws SecurityException {
+        try {
+            getService().removeStacksInWindowingModes(windowingModes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Removes stack of the activity types from the system. */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void removeStacksWithActivityTypes(int[] activityTypes) throws SecurityException {
+        try {
+            getService().removeStacksWithActivityTypes(activityTypes);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes all visible recent tasks from the system.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.REMOVE_TASKS)
+    public void removeAllVisibleRecentTasks() {
+        try {
+            getService().removeAllVisibleRecentTasks();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return the maximum number of recents entries that we will maintain and show.
+     * @hide
+     */
+    public static int getMaxRecentTasksStatic() {
+        if (sMaxRecentTasks < 0) {
+            return sMaxRecentTasks = ActivityManager.isLowRamDeviceStatic() ? 36 : 48;
+        }
+        return sMaxRecentTasks;
+    }
+
+    /**
+     * Return the default limit on the number of recents that an app can make.
+     * @hide
+     */
+    public static int getDefaultAppRecentsLimitStatic() {
+        return getMaxRecentTasksStatic() / 6;
+    }
+
+    /**
+     * Return the maximum limit on the number of recents that an app can make.
+     * @hide
+     */
+    public static int getMaxAppRecentsLimitStatic() {
+        return getMaxRecentTasksStatic() / 2;
+    }
+
+    /**
+     * Returns true if the system supports at least one form of multi-window.
+     * E.g. freeform, split-screen, picture-in-picture.
+     */
+    public static boolean supportsMultiWindow(Context context) {
+        // On watches, multi-window is used to present essential system UI, and thus it must be
+        // supported regardless of device memory characteristics.
+        boolean isWatch = context.getPackageManager().hasSystemFeature(
+                PackageManager.FEATURE_WATCH);
+        return (!ActivityManager.isLowRamDeviceStatic() || isWatch)
+                && Resources.getSystem().getBoolean(
+                com.android.internal.R.bool.config_supportsMultiWindow);
+    }
+
+    /** Returns true if the system supports split screen multi-window. */
+    public static boolean supportsSplitScreenMultiWindow(Context context) {
+        return supportsMultiWindow(context)
+                && Resources.getSystem().getBoolean(
+                com.android.internal.R.bool.config_supportsSplitScreenMultiWindow);
+    }
+
+    /**
+     * Moves the top activity in the input stackId to the pinned stack.
+     * @param stackId Id of stack to move the top activity to pinned stack.
+     * @param bounds Bounds to use for pinned stack.
+     * @return True if the top activity of stack was successfully moved to the pinned stack.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public boolean moveTopActivityToPinnedStack(int stackId, Rect bounds) {
+        try {
+            return getService().moveTopActivityToPinnedStack(stackId, bounds);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Start to enter lock task mode for given task by system(UI).
+     * @param taskId Id of task to lock.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void startSystemLockTaskMode(int taskId) {
+        try {
+            getService().startSystemLockTaskMode(taskId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Stop lock task mode by system(UI).
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void stopSystemLockTaskMode() {
+        try {
+            getService().stopSystemLockTaskMode();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Move task to stack with given id.
+     * @param taskId Id of the task to move.
+     * @param stackId Id of the stack for task moving.
+     * @param toTop Whether the given task should shown to top of stack.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void moveTaskToStack(int taskId, int stackId, boolean toTop) {
+        try {
+            getService().moveTaskToStack(taskId, stackId, toTop);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Resize the input stack id to the given bounds with animate setting.
+     * @param stackId Id of the stack to resize.
+     * @param bounds Bounds to resize the stack to or {@code null} for fullscreen.
+     * @param animate Whether we should play an animation for resizing stack.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void resizeStack(int stackId, Rect bounds, boolean animate) {
+        try {
+            getService().resizeStack(stackId, bounds, false, false, animate /* animate */,
+                    -1 /* animationDuration */);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Resize task to given bounds.
+     * @param taskId Id of task to resize.
+     * @param bounds Bounds to resize task.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void resizeTask(int taskId, Rect bounds) {
+        try {
+            getService().resizeTask(taskId, bounds, RESIZE_MODE_SYSTEM);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Resize docked stack & its task to given stack & task bounds.
+     * @param stackBounds Bounds to resize stack.
+     * @param taskBounds Bounds to resize task.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void resizeDockedStack(Rect stackBounds, Rect taskBounds) {
+        try {
+            getService().resizeDockedStack(stackBounds, taskBounds, null, null, null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * List all activity stacks information.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public String listAllStacks() {
+        final List<ActivityManager.StackInfo> stacks;
+        try {
+            stacks = getService().getAllStackInfos();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        final StringBuilder sb = new StringBuilder();
+        if (stacks != null) {
+            for (ActivityManager.StackInfo info : stacks) {
+                sb.append(info).append("\n");
+            }
+        }
+        return sb.toString();
+    }
+
+    /**
+     * Clears launch params for the given package.
+     * @param packageNames the names of the packages of which the launch params are to be cleared
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void clearLaunchParamsForPackages(List<String> packageNames) {
+        try {
+            getService().clearLaunchParamsForPackages(packageNames);
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Makes the display with the given id a single task instance display. I.e the display can only
+     * contain one task.
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS)
+    public void setDisplayToSingleTaskInstance(int displayId) {
+        try {
+            getService().setDisplayToSingleTaskInstance(displayId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/android/app/ActivityThread.java b/android/app/ActivityThread.java
new file mode 100644
index 0000000..546f000
--- /dev/null
+++ b/android/app/ActivityThread.java
@@ -0,0 +1,7371 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import static android.app.ActivityManager.PROCESS_STATE_UNKNOWN;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_CREATE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_DESTROY;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_PAUSE;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_RESUME;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_START;
+import static android.app.servertransaction.ActivityLifecycleItem.ON_STOP;
+import static android.app.servertransaction.ActivityLifecycleItem.PRE_ON_CREATE;
+import static android.content.ContentResolver.DEPRECATE_DATA_COLUMNS;
+import static android.content.ContentResolver.DEPRECATE_DATA_PREFIX;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.app.assist.AssistContent;
+import android.app.assist.AssistStructure;
+import android.app.backup.BackupAgent;
+import android.app.servertransaction.ActivityLifecycleItem;
+import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
+import android.app.servertransaction.ActivityRelaunchItem;
+import android.app.servertransaction.ActivityResultItem;
+import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
+import android.app.servertransaction.PendingTransactionActions;
+import android.app.servertransaction.PendingTransactionActions.StopInfo;
+import android.app.servertransaction.TransactionExecutor;
+import android.app.servertransaction.TransactionExecutorHelper;
+import android.content.AutofillOptions;
+import android.content.BroadcastReceiver;
+import android.content.ComponentCallbacks2;
+import android.content.ComponentName;
+import android.content.ContentCaptureOptions;
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.IContentProvider;
+import android.content.IIntentReceiver;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.AssetManager;
+import android.content.res.CompatibilityInfo;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteDebug;
+import android.database.sqlite.SQLiteDebug.DbStats;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.HardwareRenderer;
+import android.graphics.ImageDecoder;
+import android.hardware.display.DisplayManagerGlobal;
+import android.net.ConnectivityManager;
+import android.net.IConnectivityManager;
+import android.net.Proxy;
+import android.net.Uri;
+import android.os.AsyncTask;
+import android.os.Binder;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.Debug;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.GraphicsEnvironment;
+import android.os.Handler;
+import android.os.HandlerExecutor;
+import android.os.IBinder;
+import android.os.ICancellationSignal;
+import android.os.LocaleList;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.Parcel;
+import android.os.ParcelFileDescriptor;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.StrictMode;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.Trace;
+import android.os.UserHandle;
+import android.provider.BlockedNumberContract;
+import android.provider.CalendarContract;
+import android.provider.CallLog;
+import android.provider.ContactsContract;
+import android.provider.Downloads;
+import android.provider.FontsContract;
+import android.provider.Settings;
+import android.renderscript.RenderScriptCacheDir;
+import android.security.NetworkSecurityPolicy;
+import android.security.net.config.NetworkSecurityConfigProvider;
+import android.system.ErrnoException;
+import android.system.OsConstants;
+import android.system.StructStat;
+import android.util.AndroidRuntimeException;
+import android.util.ArrayMap;
+import android.util.DisplayMetrics;
+import android.util.EventLog;
+import android.util.Log;
+import android.util.LogPrinter;
+import android.util.MergedConfiguration;
+import android.util.Pair;
+import android.util.PrintWriterPrinter;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseIntArray;
+import android.util.SuperNotCalledException;
+import android.util.UtilConfig;
+import android.util.proto.ProtoOutputStream;
+import android.view.Choreographer;
+import android.view.ContextThemeWrapper;
+import android.view.Display;
+import android.view.ThreadedRenderer;
+import android.view.View;
+import android.view.ViewDebug;
+import android.view.ViewManager;
+import android.view.ViewRootImpl;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.webkit.WebView;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.app.IVoiceInteractor;
+import com.android.internal.content.ReferrerIntent;
+import com.android.internal.os.BinderInternal;
+import com.android.internal.os.RuntimeInit;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FastPrintWriter;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.function.pooled.PooledLambda;
+import com.android.org.conscrypt.OpenSSLSocketImpl;
+import com.android.org.conscrypt.TrustedCertificateStore;
+import com.android.server.am.MemInfoDumpProto;
+
+import dalvik.system.CloseGuard;
+import dalvik.system.VMDebug;
+import dalvik.system.VMRuntime;
+
+import libcore.io.ForwardingOs;
+import libcore.io.IoUtils;
+import libcore.io.Os;
+import libcore.net.event.NetworkEventDispatcher;
+
+import org.apache.harmony.dalvik.ddmc.DdmVmInternal;
+
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Method;
+import java.net.InetAddress;
+import java.nio.file.Files;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TimeZone;
+import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicInteger;
+
+final class RemoteServiceException extends AndroidRuntimeException {
+    public RemoteServiceException(String msg) {
+        super(msg);
+    }
+}
+
+/**
+ * This manages the execution of the main thread in an
+ * application process, scheduling and executing activities,
+ * broadcasts, and other operations on it as the activity
+ * manager requests.
+ *
+ * {@hide}
+ */
+public final class ActivityThread extends ClientTransactionHandler {
+    /** @hide */
+    public static final String TAG = "ActivityThread";
+    private static final android.graphics.Bitmap.Config THUMBNAIL_FORMAT = Bitmap.Config.RGB_565;
+    static final boolean localLOGV = false;
+    static final boolean DEBUG_MESSAGES = false;
+    /** @hide */
+    public static final boolean DEBUG_BROADCAST = false;
+    private static final boolean DEBUG_RESULTS = false;
+    private static final boolean DEBUG_BACKUP = false;
+    public static final boolean DEBUG_CONFIGURATION = false;
+    private static final boolean DEBUG_SERVICE = false;
+    public static final boolean DEBUG_MEMORY_TRIM = false;
+    private static final boolean DEBUG_PROVIDER = false;
+    public static final boolean DEBUG_ORDER = false;
+    private static final long MIN_TIME_BETWEEN_GCS = 5*1000;
+    /**
+     * If the activity doesn't become idle in time, the timeout will ensure to apply the pending top
+     * process state.
+     */
+    private static final long PENDING_TOP_PROCESS_STATE_TIMEOUT = 1000;
+    /**
+     * The delay to release the provider when it has no more references. It reduces the number of
+     * transactions for acquiring and releasing provider if the client accesses the provider
+     * frequently in a short time.
+     */
+    private static final long CONTENT_PROVIDER_RETAIN_TIME = 1000;
+    private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003;
+
+    /** Type for IActivityManager.serviceDoneExecuting: anonymous operation */
+    public static final int SERVICE_DONE_EXECUTING_ANON = 0;
+    /** Type for IActivityManager.serviceDoneExecuting: done with an onStart call */
+    public static final int SERVICE_DONE_EXECUTING_START = 1;
+    /** Type for IActivityManager.serviceDoneExecuting: done stopping (destroying) service */
+    public static final int SERVICE_DONE_EXECUTING_STOP = 2;
+
+    // Whether to invoke an activity callback after delivering new configuration.
+    private static final boolean REPORT_TO_ACTIVITY = true;
+
+    /** Use foreground GC policy (less pause time) and higher JIT weight. */
+    private static final int VM_PROCESS_STATE_JANK_PERCEPTIBLE = 0;
+    /** Use background GC policy and default JIT threshold. */
+    private static final int VM_PROCESS_STATE_JANK_IMPERCEPTIBLE = 1;
+
+    /**
+     * Denotes an invalid sequence number corresponding to a process state change.
+     */
+    public static final long INVALID_PROC_STATE_SEQ = -1;
+
+    /**
+     * Identifier for the sequence no. associated with this process start. It will be provided
+     * as one of the arguments when the process starts.
+     */
+    public static final String PROC_START_SEQ_IDENT = "seq=";
+
+    private final Object mNetworkPolicyLock = new Object();
+
+    /**
+     * Denotes the sequence number of the process state change for which the main thread needs
+     * to block until the network rules are updated for it.
+     *
+     * Value of {@link #INVALID_PROC_STATE_SEQ} indicates there is no need for blocking.
+     */
+    @GuardedBy("mNetworkPolicyLock")
+    private long mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
+
+    @UnsupportedAppUsage
+    private ContextImpl mSystemContext;
+    private ContextImpl mSystemUiContext;
+
+    @UnsupportedAppUsage
+    static volatile IPackageManager sPackageManager;
+
+    @UnsupportedAppUsage
+    final ApplicationThread mAppThread = new ApplicationThread();
+    @UnsupportedAppUsage
+    final Looper mLooper = Looper.myLooper();
+    @UnsupportedAppUsage
+    final H mH = new H();
+    final Executor mExecutor = new HandlerExecutor(mH);
+    /**
+     * Maps from activity token to local record of running activities in this process.
+     *
+     * This variable is readable if the code is running in activity thread or holding {@link
+     * #mResourcesManager}. It's only writable if the code is running in activity thread and holding
+     * {@link #mResourcesManager}.
+     */
+    @UnsupportedAppUsage
+    final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
+    /** The activities to be truly destroyed (not include relaunch). */
+    final Map<IBinder, ClientTransactionItem> mActivitiesToBeDestroyed =
+            Collections.synchronizedMap(new ArrayMap<IBinder, ClientTransactionItem>());
+    // List of new activities (via ActivityRecord.nextIdle) that should
+    // be reported when next we idle.
+    ActivityClientRecord mNewActivities = null;
+    // Number of activities that are currently visible on-screen.
+    @UnsupportedAppUsage
+    int mNumVisibleActivities = 0;
+    private final AtomicInteger mNumLaunchingActivities = new AtomicInteger();
+    @GuardedBy("mAppThread")
+    private int mLastProcessState = PROCESS_STATE_UNKNOWN;
+    @GuardedBy("mAppThread")
+    private int mPendingProcessState = PROCESS_STATE_UNKNOWN;
+    ArrayList<WeakReference<AssistStructure>> mLastAssistStructures = new ArrayList<>();
+    private int mLastSessionId;
+    @UnsupportedAppUsage
+    final ArrayMap<IBinder, Service> mServices = new ArrayMap<>();
+    @UnsupportedAppUsage
+    AppBindData mBoundApplication;
+    Profiler mProfiler;
+    @UnsupportedAppUsage
+    int mCurDefaultDisplayDpi;
+    @UnsupportedAppUsage
+    boolean mDensityCompatMode;
+    @UnsupportedAppUsage
+    Configuration mConfiguration;
+    Configuration mCompatConfiguration;
+    @UnsupportedAppUsage
+    Application mInitialApplication;
+    @UnsupportedAppUsage
+    final ArrayList<Application> mAllApplications
+            = new ArrayList<Application>();
+    /**
+     * Bookkeeping of instantiated backup agents indexed first by user id, then by package name.
+     * Indexing by user id supports parallel backups across users on system packages as they run in
+     * the same process with the same package name. Indexing by package name supports multiple
+     * distinct applications running in the same process.
+     */
+    private final SparseArray<ArrayMap<String, BackupAgent>> mBackupAgentsByUser =
+            new SparseArray<>();
+    /** Reference to singleton {@link ActivityThread} */
+    @UnsupportedAppUsage
+    private static volatile ActivityThread sCurrentActivityThread;
+    @UnsupportedAppUsage
+    Instrumentation mInstrumentation;
+    String mInstrumentationPackageName = null;
+    @UnsupportedAppUsage
+    String mInstrumentationAppDir = null;
+    String[] mInstrumentationSplitAppDirs = null;
+    String mInstrumentationLibDir = null;
+    @UnsupportedAppUsage
+    String mInstrumentedAppDir = null;
+    String[] mInstrumentedSplitAppDirs = null;
+    String mInstrumentedLibDir = null;
+    boolean mSystemThread = false;
+    boolean mSomeActivitiesChanged = false;
+    boolean mUpdatingSystemConfig = false;
+    /* package */ boolean mHiddenApiWarningShown = false;
+
+    // These can be accessed by multiple threads; mResourcesManager is the lock.
+    // XXX For now we keep around information about all packages we have
+    // seen, not removing entries from this map.
+    // NOTE: The activity and window managers need to call in to
+    // ActivityThread to do things like update resource configurations,
+    // which means this lock gets held while the activity and window managers
+    // holds their own lock.  Thus you MUST NEVER call back into the activity manager
+    // or window manager or anything that depends on them while holding this lock.
+    // These LoadedApk are only valid for the userId that we're running as.
+    @GuardedBy("mResourcesManager")
+    @UnsupportedAppUsage
+    final ArrayMap<String, WeakReference<LoadedApk>> mPackages = new ArrayMap<>();
+    @GuardedBy("mResourcesManager")
+    @UnsupportedAppUsage
+    final ArrayMap<String, WeakReference<LoadedApk>> mResourcePackages = new ArrayMap<>();
+    @GuardedBy("mResourcesManager")
+    final ArrayList<ActivityClientRecord> mRelaunchingActivities = new ArrayList<>();
+    @GuardedBy("mResourcesManager")
+    @UnsupportedAppUsage
+    Configuration mPendingConfiguration = null;
+    // An executor that performs multi-step transactions.
+    private final TransactionExecutor mTransactionExecutor = new TransactionExecutor(this);
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private final ResourcesManager mResourcesManager;
+
+    // Registry of remote cancellation transports pending a reply with reply handles.
+    @GuardedBy("this")
+    private @Nullable Map<SafeCancellationTransport, CancellationSignal> mRemoteCancellations;
+
+    private static final class ProviderKey {
+        final String authority;
+        final int userId;
+
+        public ProviderKey(String authority, int userId) {
+            this.authority = authority;
+            this.userId = userId;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o instanceof ProviderKey) {
+                final ProviderKey other = (ProviderKey) o;
+                return Objects.equals(authority, other.authority) && userId == other.userId;
+            }
+            return false;
+        }
+
+        @Override
+        public int hashCode() {
+            return ((authority != null) ? authority.hashCode() : 0) ^ userId;
+        }
+    }
+
+    // The lock of mProviderMap protects the following variables.
+    @UnsupportedAppUsage
+    final ArrayMap<ProviderKey, ProviderClientRecord> mProviderMap
+        = new ArrayMap<ProviderKey, ProviderClientRecord>();
+    @UnsupportedAppUsage
+    final ArrayMap<IBinder, ProviderRefCount> mProviderRefCountMap
+        = new ArrayMap<IBinder, ProviderRefCount>();
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    final ArrayMap<IBinder, ProviderClientRecord> mLocalProviders
+        = new ArrayMap<IBinder, ProviderClientRecord>();
+    @UnsupportedAppUsage
+    final ArrayMap<ComponentName, ProviderClientRecord> mLocalProvidersByName
+            = new ArrayMap<ComponentName, ProviderClientRecord>();
+
+    // Mitigation for b/74523247: Used to serialize calls to AM.getContentProvider().
+    // Note we never removes items from this map but that's okay because there are only so many
+    // users and so many authorities.
+    // TODO Remove it once we move CPR.wait() from AMS to the client side.
+    @GuardedBy("mGetProviderLocks")
+    final ArrayMap<ProviderKey, Object> mGetProviderLocks = new ArrayMap<>();
+
+    final ArrayMap<Activity, ArrayList<OnActivityPausedListener>> mOnPauseListeners
+        = new ArrayMap<Activity, ArrayList<OnActivityPausedListener>>();
+
+    final GcIdler mGcIdler = new GcIdler();
+    final PurgeIdler mPurgeIdler = new PurgeIdler();
+
+    boolean mPurgeIdlerScheduled = false;
+    boolean mGcIdlerScheduled = false;
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    static volatile Handler sMainThreadHandler;  // set once in main()
+
+    Bundle mCoreSettings = null;
+
+    /** Activity client record, used for bookkeeping for the real {@link Activity} instance. */
+    public static final class ActivityClientRecord {
+        @UnsupportedAppUsage
+        public IBinder token;
+        public IBinder assistToken;
+        int ident;
+        @UnsupportedAppUsage
+        Intent intent;
+        String referrer;
+        IVoiceInteractor voiceInteractor;
+        Bundle state;
+        PersistableBundle persistentState;
+        @UnsupportedAppUsage
+        Activity activity;
+        Window window;
+        Activity parent;
+        String embeddedID;
+        Activity.NonConfigurationInstances lastNonConfigurationInstances;
+        // TODO(lifecycler): Use mLifecycleState instead.
+        @UnsupportedAppUsage
+        boolean paused;
+        @UnsupportedAppUsage
+        boolean stopped;
+        boolean hideForNow;
+        Configuration newConfig;
+        Configuration createdConfig;
+        Configuration overrideConfig;
+        // Used to save the last reported configuration from server side so that activity
+        // configuration transactions can always use the latest configuration.
+        @GuardedBy("this")
+        private Configuration mPendingOverrideConfig;
+        // Used for consolidating configs before sending on to Activity.
+        private Configuration tmpConfig = new Configuration();
+        // Callback used for updating activity override config.
+        ViewRootImpl.ActivityConfigCallback configCallback;
+        ActivityClientRecord nextIdle;
+
+        // Indicates whether this activity is currently the topmost resumed one in the system.
+        // This holds the last reported value from server.
+        boolean isTopResumedActivity;
+        // This holds the value last sent to the activity. This is needed, because an update from
+        // server may come at random time, but we always need to report changes between ON_RESUME
+        // and ON_PAUSE to the app.
+        boolean lastReportedTopResumedState;
+
+        ProfilerInfo profilerInfo;
+
+        @UnsupportedAppUsage
+        ActivityInfo activityInfo;
+        @UnsupportedAppUsage
+        CompatibilityInfo compatInfo;
+        @UnsupportedAppUsage
+        public LoadedApk packageInfo;
+
+        List<ResultInfo> pendingResults;
+        List<ReferrerIntent> pendingIntents;
+
+        boolean startsNotResumed;
+        public final boolean isForward;
+        int pendingConfigChanges;
+
+        Window mPendingRemoveWindow;
+        WindowManager mPendingRemoveWindowManager;
+        @UnsupportedAppUsage
+        boolean mPreserveWindow;
+
+        @LifecycleState
+        private int mLifecycleState = PRE_ON_CREATE;
+
+        @VisibleForTesting
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+        public ActivityClientRecord() {
+            this.isForward = false;
+            init();
+        }
+
+        public ActivityClientRecord(IBinder token, Intent intent, int ident,
+                ActivityInfo info, Configuration overrideConfig, CompatibilityInfo compatInfo,
+                String referrer, IVoiceInteractor voiceInteractor, Bundle state,
+                PersistableBundle persistentState, List<ResultInfo> pendingResults,
+                List<ReferrerIntent> pendingNewIntents, boolean isForward,
+                ProfilerInfo profilerInfo, ClientTransactionHandler client,
+                IBinder assistToken) {
+            this.token = token;
+            this.assistToken = assistToken;
+            this.ident = ident;
+            this.intent = intent;
+            this.referrer = referrer;
+            this.voiceInteractor = voiceInteractor;
+            this.activityInfo = info;
+            this.compatInfo = compatInfo;
+            this.state = state;
+            this.persistentState = persistentState;
+            this.pendingResults = pendingResults;
+            this.pendingIntents = pendingNewIntents;
+            this.isForward = isForward;
+            this.profilerInfo = profilerInfo;
+            this.overrideConfig = overrideConfig;
+            this.packageInfo = client.getPackageInfoNoCheck(activityInfo.applicationInfo,
+                    compatInfo);
+            init();
+        }
+
+        /** Common initializer for all constructors. */
+        private void init() {
+            parent = null;
+            embeddedID = null;
+            paused = false;
+            stopped = false;
+            hideForNow = false;
+            nextIdle = null;
+            configCallback = (Configuration overrideConfig, int newDisplayId) -> {
+                if (activity == null) {
+                    throw new IllegalStateException(
+                            "Received config update for non-existing activity");
+                }
+                activity.mMainThread.handleActivityConfigurationChanged(token, overrideConfig,
+                        newDisplayId);
+            };
+        }
+
+        /** Get the current lifecycle state. */
+        public int getLifecycleState() {
+            return mLifecycleState;
+        }
+
+        /** Update the current lifecycle state for internal bookkeeping. */
+        public void setState(@LifecycleState int newLifecycleState) {
+            mLifecycleState = newLifecycleState;
+            switch (mLifecycleState) {
+                case ON_CREATE:
+                    paused = true;
+                    stopped = true;
+                    break;
+                case ON_START:
+                    paused = true;
+                    stopped = false;
+                    break;
+                case ON_RESUME:
+                    paused = false;
+                    stopped = false;
+                    break;
+                case ON_PAUSE:
+                    paused = true;
+                    stopped = false;
+                    break;
+                case ON_STOP:
+                    paused = true;
+                    stopped = true;
+                    break;
+            }
+        }
+
+        private boolean isPreHoneycomb() {
+            return activity != null && activity.getApplicationInfo().targetSdkVersion
+                    < android.os.Build.VERSION_CODES.HONEYCOMB;
+        }
+
+        private boolean isPreP() {
+            return activity != null && activity.getApplicationInfo().targetSdkVersion
+                    < android.os.Build.VERSION_CODES.P;
+        }
+
+        public boolean isPersistable() {
+            return activityInfo.persistableMode == ActivityInfo.PERSIST_ACROSS_REBOOTS;
+        }
+
+        public boolean isVisibleFromServer() {
+            return activity != null && activity.mVisibleFromServer;
+        }
+
+        public String toString() {
+            ComponentName componentName = intent != null ? intent.getComponent() : null;
+            return "ActivityRecord{"
+                + Integer.toHexString(System.identityHashCode(this))
+                + " token=" + token + " " + (componentName == null
+                        ? "no component name" : componentName.toShortString())
+                + "}";
+        }
+
+        public String getStateString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("ActivityClientRecord{");
+            sb.append("paused=").append(paused);
+            sb.append(", stopped=").append(stopped);
+            sb.append(", hideForNow=").append(hideForNow);
+            sb.append(", startsNotResumed=").append(startsNotResumed);
+            sb.append(", isForward=").append(isForward);
+            sb.append(", pendingConfigChanges=").append(pendingConfigChanges);
+            sb.append(", preserveWindow=").append(mPreserveWindow);
+            if (activity != null) {
+                sb.append(", Activity{");
+                sb.append("resumed=").append(activity.mResumed);
+                sb.append(", stopped=").append(activity.mStopped);
+                sb.append(", finished=").append(activity.isFinishing());
+                sb.append(", destroyed=").append(activity.isDestroyed());
+                sb.append(", startedActivity=").append(activity.mStartedActivity);
+                sb.append(", changingConfigurations=").append(activity.mChangingConfigurations);
+                sb.append("}");
+            }
+            sb.append("}");
+            return sb.toString();
+        }
+    }
+
+    final class ProviderClientRecord {
+        final String[] mNames;
+        @UnsupportedAppUsage
+        final IContentProvider mProvider;
+        @UnsupportedAppUsage
+        final ContentProvider mLocalProvider;
+        @UnsupportedAppUsage
+        final ContentProviderHolder mHolder;
+
+        ProviderClientRecord(String[] names, IContentProvider provider,
+                ContentProvider localProvider, ContentProviderHolder holder) {
+            mNames = names;
+            mProvider = provider;
+            mLocalProvider = localProvider;
+            mHolder = holder;
+        }
+    }
+
+    static final class ReceiverData extends BroadcastReceiver.PendingResult {
+        public ReceiverData(Intent intent, int resultCode, String resultData, Bundle resultExtras,
+                boolean ordered, boolean sticky, IBinder token, int sendingUser) {
+            super(resultCode, resultData, resultExtras, TYPE_COMPONENT, ordered, sticky,
+                    token, sendingUser, intent.getFlags());
+            this.intent = intent;
+        }
+
+        @UnsupportedAppUsage
+        Intent intent;
+        @UnsupportedAppUsage
+        ActivityInfo info;
+        @UnsupportedAppUsage
+        CompatibilityInfo compatInfo;
+        public String toString() {
+            return "ReceiverData{intent=" + intent + " packageName=" +
+                    info.packageName + " resultCode=" + getResultCode()
+                    + " resultData=" + getResultData() + " resultExtras="
+                    + getResultExtras(false) + "}";
+        }
+    }
+
+    static final class CreateBackupAgentData {
+        ApplicationInfo appInfo;
+        CompatibilityInfo compatInfo;
+        int backupMode;
+        int userId;
+        public String toString() {
+            return "CreateBackupAgentData{appInfo=" + appInfo
+                    + " backupAgent=" + appInfo.backupAgentName
+                    + " mode=" + backupMode + " userId=" + userId + "}";
+        }
+    }
+
+    static final class CreateServiceData {
+        @UnsupportedAppUsage
+        IBinder token;
+        @UnsupportedAppUsage
+        ServiceInfo info;
+        @UnsupportedAppUsage
+        CompatibilityInfo compatInfo;
+        @UnsupportedAppUsage
+        Intent intent;
+        public String toString() {
+            return "CreateServiceData{token=" + token + " className="
+            + info.name + " packageName=" + info.packageName
+            + " intent=" + intent + "}";
+        }
+    }
+
+    static final class BindServiceData {
+        @UnsupportedAppUsage
+        IBinder token;
+        @UnsupportedAppUsage
+        Intent intent;
+        boolean rebind;
+        public String toString() {
+            return "BindServiceData{token=" + token + " intent=" + intent + "}";
+        }
+    }
+
+    static final class ServiceArgsData {
+        @UnsupportedAppUsage
+        IBinder token;
+        boolean taskRemoved;
+        int startId;
+        int flags;
+        @UnsupportedAppUsage
+        Intent args;
+        public String toString() {
+            return "ServiceArgsData{token=" + token + " startId=" + startId
+            + " args=" + args + "}";
+        }
+    }
+
+    static final class AppBindData {
+        @UnsupportedAppUsage
+        LoadedApk info;
+        @UnsupportedAppUsage
+        String processName;
+        @UnsupportedAppUsage
+        ApplicationInfo appInfo;
+        @UnsupportedAppUsage
+        List<ProviderInfo> providers;
+        ComponentName instrumentationName;
+        @UnsupportedAppUsage
+        Bundle instrumentationArgs;
+        IInstrumentationWatcher instrumentationWatcher;
+        IUiAutomationConnection instrumentationUiAutomationConnection;
+        int debugMode;
+        boolean enableBinderTracking;
+        boolean trackAllocation;
+        @UnsupportedAppUsage
+        boolean restrictedBackupMode;
+        @UnsupportedAppUsage
+        boolean persistent;
+        Configuration config;
+        @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+        CompatibilityInfo compatInfo;
+        String buildSerial;
+
+        /** Initial values for {@link Profiler}. */
+        ProfilerInfo initProfilerInfo;
+
+        AutofillOptions autofillOptions;
+
+        /**
+         * Content capture options for the application - when null, it means ContentCapture is not
+         * enabled for the package.
+         */
+        @Nullable
+        ContentCaptureOptions contentCaptureOptions;
+
+        @Override
+        public String toString() {
+            return "AppBindData{appInfo=" + appInfo + "}";
+        }
+    }
+
+    static final class Profiler {
+        String profileFile;
+        ParcelFileDescriptor profileFd;
+        int samplingInterval;
+        boolean autoStopProfiler;
+        boolean streamingOutput;
+        boolean profiling;
+        boolean handlingProfiling;
+        public void setProfiler(ProfilerInfo profilerInfo) {
+            ParcelFileDescriptor fd = profilerInfo.profileFd;
+            if (profiling) {
+                if (fd != null) {
+                    try {
+                        fd.close();
+                    } catch (IOException e) {
+                        // Ignore
+                    }
+                }
+                return;
+            }
+            if (profileFd != null) {
+                try {
+                    profileFd.close();
+                } catch (IOException e) {
+                    // Ignore
+                }
+            }
+            profileFile = profilerInfo.profileFile;
+            profileFd = fd;
+            samplingInterval = profilerInfo.samplingInterval;
+            autoStopProfiler = profilerInfo.autoStopProfiler;
+            streamingOutput = profilerInfo.streamingOutput;
+        }
+        public void startProfiling() {
+            if (profileFd == null || profiling) {
+                return;
+            }
+            try {
+                int bufferSize = SystemProperties.getInt("debug.traceview-buffer-size-mb", 8);
+                VMDebug.startMethodTracing(profileFile, profileFd.getFileDescriptor(),
+                        bufferSize * 1024 * 1024, 0, samplingInterval != 0, samplingInterval,
+                        streamingOutput);
+                profiling = true;
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Profiling failed on path " + profileFile, e);
+                try {
+                    profileFd.close();
+                    profileFd = null;
+                } catch (IOException e2) {
+                    Slog.w(TAG, "Failure closing profile fd", e2);
+                }
+            }
+        }
+        public void stopProfiling() {
+            if (profiling) {
+                profiling = false;
+                Debug.stopMethodTracing();
+                if (profileFd != null) {
+                    try {
+                        profileFd.close();
+                    } catch (IOException e) {
+                    }
+                }
+                profileFd = null;
+                profileFile = null;
+            }
+        }
+    }
+
+    static final class DumpComponentInfo {
+        ParcelFileDescriptor fd;
+        IBinder token;
+        String prefix;
+        String[] args;
+    }
+
+    static final class ContextCleanupInfo {
+        ContextImpl context;
+        String what;
+        String who;
+    }
+
+    static final class DumpHeapData {
+        // Whether to dump the native or managed heap.
+        public boolean managed;
+        public boolean mallocInfo;
+        public boolean runGc;
+        String path;
+        ParcelFileDescriptor fd;
+        RemoteCallback finishCallback;
+    }
+
+    static final class UpdateCompatibilityData {
+        String pkg;
+        CompatibilityInfo info;
+    }
+
+    static final class RequestAssistContextExtras {
+        IBinder activityToken;
+        IBinder requestToken;
+        int requestType;
+        int sessionId;
+        int flags;
+    }
+
+    private class ApplicationThread extends IApplicationThread.Stub {
+        private static final String DB_INFO_FORMAT = "  %8s %8s %14s %14s  %s";
+
+        public final void scheduleSleeping(IBinder token, boolean sleeping) {
+            sendMessage(H.SLEEPING, token, sleeping ? 1 : 0);
+        }
+
+        public final void scheduleReceiver(Intent intent, ActivityInfo info,
+                CompatibilityInfo compatInfo, int resultCode, String data, Bundle extras,
+                boolean sync, int sendingUser, int processState) {
+            updateProcessState(processState, false);
+            ReceiverData r = new ReceiverData(intent, resultCode, data, extras,
+                    sync, false, mAppThread.asBinder(), sendingUser);
+            r.info = info;
+            r.compatInfo = compatInfo;
+            sendMessage(H.RECEIVER, r);
+        }
+
+        public final void scheduleCreateBackupAgent(ApplicationInfo app,
+                CompatibilityInfo compatInfo, int backupMode, int userId) {
+            CreateBackupAgentData d = new CreateBackupAgentData();
+            d.appInfo = app;
+            d.compatInfo = compatInfo;
+            d.backupMode = backupMode;
+            d.userId = userId;
+
+            sendMessage(H.CREATE_BACKUP_AGENT, d);
+        }
+
+        public final void scheduleDestroyBackupAgent(ApplicationInfo app,
+                CompatibilityInfo compatInfo, int userId) {
+            CreateBackupAgentData d = new CreateBackupAgentData();
+            d.appInfo = app;
+            d.compatInfo = compatInfo;
+            d.userId = userId;
+
+            sendMessage(H.DESTROY_BACKUP_AGENT, d);
+        }
+
+        public final void scheduleCreateService(IBinder token,
+                ServiceInfo info, CompatibilityInfo compatInfo, int processState) {
+            updateProcessState(processState, false);
+            CreateServiceData s = new CreateServiceData();
+            s.token = token;
+            s.info = info;
+            s.compatInfo = compatInfo;
+
+            sendMessage(H.CREATE_SERVICE, s);
+        }
+
+        public final void scheduleBindService(IBinder token, Intent intent,
+                boolean rebind, int processState) {
+            updateProcessState(processState, false);
+            BindServiceData s = new BindServiceData();
+            s.token = token;
+            s.intent = intent;
+            s.rebind = rebind;
+
+            if (DEBUG_SERVICE)
+                Slog.v(TAG, "scheduleBindService token=" + token + " intent=" + intent + " uid="
+                        + Binder.getCallingUid() + " pid=" + Binder.getCallingPid());
+            sendMessage(H.BIND_SERVICE, s);
+        }
+
+        public final void scheduleUnbindService(IBinder token, Intent intent) {
+            BindServiceData s = new BindServiceData();
+            s.token = token;
+            s.intent = intent;
+
+            sendMessage(H.UNBIND_SERVICE, s);
+        }
+
+        public final void scheduleServiceArgs(IBinder token, ParceledListSlice args) {
+            List<ServiceStartArgs> list = args.getList();
+
+            for (int i = 0; i < list.size(); i++) {
+                ServiceStartArgs ssa = list.get(i);
+                ServiceArgsData s = new ServiceArgsData();
+                s.token = token;
+                s.taskRemoved = ssa.taskRemoved;
+                s.startId = ssa.startId;
+                s.flags = ssa.flags;
+                s.args = ssa.args;
+
+                sendMessage(H.SERVICE_ARGS, s);
+            }
+        }
+
+        public final void scheduleStopService(IBinder token) {
+            sendMessage(H.STOP_SERVICE, token);
+        }
+
+        public final void bindApplication(String processName, ApplicationInfo appInfo,
+                List<ProviderInfo> providers, ComponentName instrumentationName,
+                ProfilerInfo profilerInfo, Bundle instrumentationArgs,
+                IInstrumentationWatcher instrumentationWatcher,
+                IUiAutomationConnection instrumentationUiConnection, int debugMode,
+                boolean enableBinderTracking, boolean trackAllocation,
+                boolean isRestrictedBackupMode, boolean persistent, Configuration config,
+                CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
+                String buildSerial, AutofillOptions autofillOptions,
+                ContentCaptureOptions contentCaptureOptions) {
+            if (services != null) {
+                if (false) {
+                    // Test code to make sure the app could see the passed-in services.
+                    for (Object oname : services.keySet()) {
+                        if (services.get(oname) == null) {
+                            continue; // AM just passed in a null service.
+                        }
+                        String name = (String) oname;
+
+                        // See b/79378449 about the following exemption.
+                        switch (name) {
+                            case "package":
+                            case Context.WINDOW_SERVICE:
+                                continue;
+                        }
+
+                        if (ServiceManager.getService(name) == null) {
+                            Log.wtf(TAG, "Service " + name + " should be accessible by this app");
+                        }
+                    }
+                }
+
+                // Setup the service cache in the ServiceManager
+                ServiceManager.initServiceCache(services);
+            }
+
+            setCoreSettings(coreSettings);
+
+            AppBindData data = new AppBindData();
+            data.processName = processName;
+            data.appInfo = appInfo;
+            data.providers = providers;
+            data.instrumentationName = instrumentationName;
+            data.instrumentationArgs = instrumentationArgs;
+            data.instrumentationWatcher = instrumentationWatcher;
+            data.instrumentationUiAutomationConnection = instrumentationUiConnection;
+            data.debugMode = debugMode;
+            data.enableBinderTracking = enableBinderTracking;
+            data.trackAllocation = trackAllocation;
+            data.restrictedBackupMode = isRestrictedBackupMode;
+            data.persistent = persistent;
+            data.config = config;
+            data.compatInfo = compatInfo;
+            data.initProfilerInfo = profilerInfo;
+            data.buildSerial = buildSerial;
+            data.autofillOptions = autofillOptions;
+            data.contentCaptureOptions = contentCaptureOptions;
+            sendMessage(H.BIND_APPLICATION, data);
+        }
+
+        public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = entryPoint;
+            args.arg2 = entryPointArgs;
+            sendMessage(H.RUN_ISOLATED_ENTRY_POINT, args);
+        }
+
+        public final void scheduleExit() {
+            sendMessage(H.EXIT_APPLICATION, null);
+        }
+
+        public final void scheduleSuicide() {
+            sendMessage(H.SUICIDE, null);
+        }
+
+        public void scheduleApplicationInfoChanged(ApplicationInfo ai) {
+            mH.removeMessages(H.APPLICATION_INFO_CHANGED, ai);
+            sendMessage(H.APPLICATION_INFO_CHANGED, ai);
+        }
+
+        public void updateTimeZone() {
+            TimeZone.setDefault(null);
+        }
+
+        public void clearDnsCache() {
+            // a non-standard API to get this to libcore
+            InetAddress.clearDnsCache();
+            // Allow libcore to perform the necessary actions as it sees fit upon a network
+            // configuration change.
+            NetworkEventDispatcher.getInstance().onNetworkConfigurationChanged();
+        }
+
+        public void updateHttpProxy() {
+            ActivityThread.updateHttpProxy(
+                    getApplication() != null ? getApplication() : getSystemContext());
+        }
+
+        public void processInBackground() {
+            mH.removeMessages(H.GC_WHEN_IDLE);
+            mH.sendMessage(mH.obtainMessage(H.GC_WHEN_IDLE));
+        }
+
+        public void dumpService(ParcelFileDescriptor pfd, IBinder servicetoken, String[] args) {
+            DumpComponentInfo data = new DumpComponentInfo();
+            try {
+                data.fd = pfd.dup();
+                data.token = servicetoken;
+                data.args = args;
+                sendMessage(H.DUMP_SERVICE, data, 0, 0, true /*async*/);
+            } catch (IOException e) {
+                Slog.w(TAG, "dumpService failed", e);
+            } finally {
+                IoUtils.closeQuietly(pfd);
+            }
+        }
+
+        // This function exists to make sure all receiver dispatching is
+        // correctly ordered, since these are one-way calls and the binder driver
+        // applies transaction ordering per object for such calls.
+        public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent,
+                int resultCode, String dataStr, Bundle extras, boolean ordered,
+                boolean sticky, int sendingUser, int processState) throws RemoteException {
+            updateProcessState(processState, false);
+            receiver.performReceive(intent, resultCode, dataStr, extras, ordered,
+                    sticky, sendingUser);
+        }
+
+        @Override
+        public void scheduleLowMemory() {
+            sendMessage(H.LOW_MEMORY, null);
+        }
+
+        @Override
+        public void profilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
+            sendMessage(H.PROFILER_CONTROL, profilerInfo, start ? 1 : 0, profileType);
+        }
+
+        @Override
+        public void dumpHeap(boolean managed, boolean mallocInfo, boolean runGc, String path,
+                ParcelFileDescriptor fd, RemoteCallback finishCallback) {
+            DumpHeapData dhd = new DumpHeapData();
+            dhd.managed = managed;
+            dhd.mallocInfo = mallocInfo;
+            dhd.runGc = runGc;
+            dhd.path = path;
+            try {
+                // Since we're going to dump the heap asynchronously, dup the file descriptor before
+                // it's closed on returning from the IPC call.
+                dhd.fd = fd.dup();
+            } catch (IOException e) {
+                Slog.e(TAG, "Failed to duplicate heap dump file descriptor", e);
+                return;
+            }
+            dhd.finishCallback = finishCallback;
+            sendMessage(H.DUMP_HEAP, dhd, 0, 0, true /*async*/);
+        }
+
+        public void attachAgent(String agent) {
+            sendMessage(H.ATTACH_AGENT, agent);
+        }
+
+        public void setSchedulingGroup(int group) {
+            // Note: do this immediately, since going into the foreground
+            // should happen regardless of what pending work we have to do
+            // and the activity manager will wait for us to report back that
+            // we are done before sending us to the background.
+            try {
+                Process.setProcessGroup(Process.myPid(), group);
+            } catch (Exception e) {
+                Slog.w(TAG, "Failed setting process group to " + group, e);
+            }
+        }
+
+        public void dispatchPackageBroadcast(int cmd, String[] packages) {
+            sendMessage(H.DISPATCH_PACKAGE_BROADCAST, packages, cmd);
+        }
+
+        public void scheduleCrash(String msg) {
+            sendMessage(H.SCHEDULE_CRASH, msg);
+        }
+
+        public void dumpActivity(ParcelFileDescriptor pfd, IBinder activitytoken,
+                String prefix, String[] args) {
+            DumpComponentInfo data = new DumpComponentInfo();
+            try {
+                data.fd = pfd.dup();
+                data.token = activitytoken;
+                data.prefix = prefix;
+                data.args = args;
+                sendMessage(H.DUMP_ACTIVITY, data, 0, 0, true /*async*/);
+            } catch (IOException e) {
+                Slog.w(TAG, "dumpActivity failed", e);
+            } finally {
+                IoUtils.closeQuietly(pfd);
+            }
+        }
+
+        public void dumpProvider(ParcelFileDescriptor pfd, IBinder providertoken,
+                String[] args) {
+            DumpComponentInfo data = new DumpComponentInfo();
+            try {
+                data.fd = pfd.dup();
+                data.token = providertoken;
+                data.args = args;
+                sendMessage(H.DUMP_PROVIDER, data, 0, 0, true /*async*/);
+            } catch (IOException e) {
+                Slog.w(TAG, "dumpProvider failed", e);
+            } finally {
+                IoUtils.closeQuietly(pfd);
+            }
+        }
+
+        @Override
+        public void dumpMemInfo(ParcelFileDescriptor pfd, Debug.MemoryInfo mem, boolean checkin,
+                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+                boolean dumpUnreachable, String[] args) {
+            FileOutputStream fout = new FileOutputStream(pfd.getFileDescriptor());
+            PrintWriter pw = new FastPrintWriter(fout);
+            try {
+                dumpMemInfo(pw, mem, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
+            } finally {
+                pw.flush();
+                IoUtils.closeQuietly(pfd);
+            }
+        }
+
+        private void dumpMemInfo(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
+                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly, boolean dumpUnreachable) {
+            long nativeMax = Debug.getNativeHeapSize() / 1024;
+            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
+
+            Runtime runtime = Runtime.getRuntime();
+            runtime.gc();  // Do GC since countInstancesOfClass counts unreachable objects.
+            long dalvikMax = runtime.totalMemory() / 1024;
+            long dalvikFree = runtime.freeMemory() / 1024;
+            long dalvikAllocated = dalvikMax - dalvikFree;
+
+            Class[] classesToCount = new Class[] {
+                    ContextImpl.class,
+                    Activity.class,
+                    WebView.class,
+                    OpenSSLSocketImpl.class
+            };
+            long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true);
+            long appContextInstanceCount = instanceCounts[0];
+            long activityInstanceCount = instanceCounts[1];
+            long webviewInstanceCount = instanceCounts[2];
+            long openSslSocketCount = instanceCounts[3];
+
+            long viewInstanceCount = ViewDebug.getViewInstanceCount();
+            long viewRootInstanceCount = ViewDebug.getViewRootImplCount();
+            int globalAssetCount = AssetManager.getGlobalAssetCount();
+            int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
+            int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
+            int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
+            int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
+            long parcelSize = Parcel.getGlobalAllocSize();
+            long parcelCount = Parcel.getGlobalAllocCount();
+            SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
+
+            dumpMemInfoTable(pw, memInfo, checkin, dumpFullInfo, dumpDalvik, dumpSummaryOnly,
+                    Process.myPid(),
+                    (mBoundApplication != null) ? mBoundApplication.processName : "unknown",
+                    nativeMax, nativeAllocated, nativeFree,
+                    dalvikMax, dalvikAllocated, dalvikFree);
+
+            if (checkin) {
+                // NOTE: if you change anything significant below, also consider changing
+                // ACTIVITY_THREAD_CHECKIN_VERSION.
+
+                // Object counts
+                pw.print(viewInstanceCount); pw.print(',');
+                pw.print(viewRootInstanceCount); pw.print(',');
+                pw.print(appContextInstanceCount); pw.print(',');
+                pw.print(activityInstanceCount); pw.print(',');
+
+                pw.print(globalAssetCount); pw.print(',');
+                pw.print(globalAssetManagerCount); pw.print(',');
+                pw.print(binderLocalObjectCount); pw.print(',');
+                pw.print(binderProxyObjectCount); pw.print(',');
+
+                pw.print(binderDeathObjectCount); pw.print(',');
+                pw.print(openSslSocketCount); pw.print(',');
+
+                // SQL
+                pw.print(stats.memoryUsed / 1024); pw.print(',');
+                pw.print(stats.memoryUsed / 1024); pw.print(',');
+                pw.print(stats.pageCacheOverflow / 1024); pw.print(',');
+                pw.print(stats.largestMemAlloc / 1024);
+                for (int i = 0; i < stats.dbStats.size(); i++) {
+                    DbStats dbStats = stats.dbStats.get(i);
+                    pw.print(','); pw.print(dbStats.dbName);
+                    pw.print(','); pw.print(dbStats.pageSize);
+                    pw.print(','); pw.print(dbStats.dbSize);
+                    pw.print(','); pw.print(dbStats.lookaside);
+                    pw.print(','); pw.print(dbStats.cache);
+                    pw.print(','); pw.print(dbStats.cache);
+                }
+                pw.println();
+
+                return;
+            }
+
+            pw.println(" ");
+            pw.println(" Objects");
+            printRow(pw, TWO_COUNT_COLUMNS, "Views:", viewInstanceCount, "ViewRootImpl:",
+                    viewRootInstanceCount);
+
+            printRow(pw, TWO_COUNT_COLUMNS, "AppContexts:", appContextInstanceCount,
+                    "Activities:", activityInstanceCount);
+
+            printRow(pw, TWO_COUNT_COLUMNS, "Assets:", globalAssetCount,
+                    "AssetManagers:", globalAssetManagerCount);
+
+            printRow(pw, TWO_COUNT_COLUMNS, "Local Binders:", binderLocalObjectCount,
+                    "Proxy Binders:", binderProxyObjectCount);
+            printRow(pw, TWO_COUNT_COLUMNS, "Parcel memory:", parcelSize/1024,
+                    "Parcel count:", parcelCount);
+            printRow(pw, TWO_COUNT_COLUMNS, "Death Recipients:", binderDeathObjectCount,
+                    "OpenSSL Sockets:", openSslSocketCount);
+            printRow(pw, ONE_COUNT_COLUMN, "WebViews:", webviewInstanceCount);
+
+            // SQLite mem info
+            pw.println(" ");
+            pw.println(" SQL");
+            printRow(pw, ONE_COUNT_COLUMN, "MEMORY_USED:", stats.memoryUsed / 1024);
+            printRow(pw, TWO_COUNT_COLUMNS, "PAGECACHE_OVERFLOW:",
+                    stats.pageCacheOverflow / 1024, "MALLOC_SIZE:", stats.largestMemAlloc / 1024);
+            pw.println(" ");
+            int N = stats.dbStats.size();
+            if (N > 0) {
+                pw.println(" DATABASES");
+                printRow(pw, DB_INFO_FORMAT, "pgsz", "dbsz", "Lookaside(b)", "cache",
+                        "Dbname");
+                for (int i = 0; i < N; i++) {
+                    DbStats dbStats = stats.dbStats.get(i);
+                    printRow(pw, DB_INFO_FORMAT,
+                            (dbStats.pageSize > 0) ? String.valueOf(dbStats.pageSize) : " ",
+                            (dbStats.dbSize > 0) ? String.valueOf(dbStats.dbSize) : " ",
+                            (dbStats.lookaside > 0) ? String.valueOf(dbStats.lookaside) : " ",
+                            dbStats.cache, dbStats.dbName);
+                }
+            }
+
+            // Asset details.
+            String assetAlloc = AssetManager.getAssetAllocations();
+            if (assetAlloc != null) {
+                pw.println(" ");
+                pw.println(" Asset Allocations");
+                pw.print(assetAlloc);
+            }
+
+            // Unreachable native memory
+            if (dumpUnreachable) {
+                boolean showContents = ((mBoundApplication != null)
+                    && ((mBoundApplication.appInfo.flags&ApplicationInfo.FLAG_DEBUGGABLE) != 0))
+                    || android.os.Build.IS_DEBUGGABLE;
+                pw.println(" ");
+                pw.println(" Unreachable memory");
+                pw.print(Debug.getUnreachableMemory(100, showContents));
+            }
+        }
+
+        @Override
+        public void dumpMemInfoProto(ParcelFileDescriptor pfd, Debug.MemoryInfo mem,
+                boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+                boolean dumpUnreachable, String[] args) {
+            ProtoOutputStream proto = new ProtoOutputStream(pfd.getFileDescriptor());
+            try {
+                dumpMemInfo(proto, mem, dumpFullInfo, dumpDalvik, dumpSummaryOnly, dumpUnreachable);
+            } finally {
+                proto.flush();
+                IoUtils.closeQuietly(pfd);
+            }
+        }
+
+        private void dumpMemInfo(ProtoOutputStream proto, Debug.MemoryInfo memInfo,
+                boolean dumpFullInfo, boolean dumpDalvik,
+                boolean dumpSummaryOnly, boolean dumpUnreachable) {
+            long nativeMax = Debug.getNativeHeapSize() / 1024;
+            long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024;
+            long nativeFree = Debug.getNativeHeapFreeSize() / 1024;
+
+            Runtime runtime = Runtime.getRuntime();
+            runtime.gc();  // Do GC since countInstancesOfClass counts unreachable objects.
+            long dalvikMax = runtime.totalMemory() / 1024;
+            long dalvikFree = runtime.freeMemory() / 1024;
+            long dalvikAllocated = dalvikMax - dalvikFree;
+
+            Class[] classesToCount = new Class[] {
+                    ContextImpl.class,
+                    Activity.class,
+                    WebView.class,
+                    OpenSSLSocketImpl.class
+            };
+            long[] instanceCounts = VMDebug.countInstancesOfClasses(classesToCount, true);
+            long appContextInstanceCount = instanceCounts[0];
+            long activityInstanceCount = instanceCounts[1];
+            long webviewInstanceCount = instanceCounts[2];
+            long openSslSocketCount = instanceCounts[3];
+
+            long viewInstanceCount = ViewDebug.getViewInstanceCount();
+            long viewRootInstanceCount = ViewDebug.getViewRootImplCount();
+            int globalAssetCount = AssetManager.getGlobalAssetCount();
+            int globalAssetManagerCount = AssetManager.getGlobalAssetManagerCount();
+            int binderLocalObjectCount = Debug.getBinderLocalObjectCount();
+            int binderProxyObjectCount = Debug.getBinderProxyObjectCount();
+            int binderDeathObjectCount = Debug.getBinderDeathObjectCount();
+            long parcelSize = Parcel.getGlobalAllocSize();
+            long parcelCount = Parcel.getGlobalAllocCount();
+            SQLiteDebug.PagerStats stats = SQLiteDebug.getDatabaseInfo();
+
+            final long mToken = proto.start(MemInfoDumpProto.AppData.PROCESS_MEMORY);
+            proto.write(MemInfoDumpProto.ProcessMemory.PID, Process.myPid());
+            proto.write(MemInfoDumpProto.ProcessMemory.PROCESS_NAME,
+                    (mBoundApplication != null) ? mBoundApplication.processName : "unknown");
+            dumpMemInfoTable(proto, memInfo, dumpDalvik, dumpSummaryOnly,
+                    nativeMax, nativeAllocated, nativeFree,
+                    dalvikMax, dalvikAllocated, dalvikFree);
+            proto.end(mToken);
+
+            final long oToken = proto.start(MemInfoDumpProto.AppData.OBJECTS);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.VIEW_INSTANCE_COUNT,
+                    viewInstanceCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.VIEW_ROOT_INSTANCE_COUNT,
+                    viewRootInstanceCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.APP_CONTEXT_INSTANCE_COUNT,
+                    appContextInstanceCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.ACTIVITY_INSTANCE_COUNT,
+                    activityInstanceCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.GLOBAL_ASSET_COUNT,
+                    globalAssetCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.GLOBAL_ASSET_MANAGER_COUNT,
+                    globalAssetManagerCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.LOCAL_BINDER_OBJECT_COUNT,
+                    binderLocalObjectCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.PROXY_BINDER_OBJECT_COUNT,
+                    binderProxyObjectCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_MEMORY_KB,
+                    parcelSize / 1024);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.PARCEL_COUNT, parcelCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.BINDER_OBJECT_DEATH_COUNT,
+                    binderDeathObjectCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.OPEN_SSL_SOCKET_COUNT,
+                    openSslSocketCount);
+            proto.write(MemInfoDumpProto.AppData.ObjectStats.WEBVIEW_INSTANCE_COUNT,
+                    webviewInstanceCount);
+            proto.end(oToken);
+
+            // SQLite mem info
+            final long sToken = proto.start(MemInfoDumpProto.AppData.SQL);
+            proto.write(MemInfoDumpProto.AppData.SqlStats.MEMORY_USED_KB,
+                    stats.memoryUsed / 1024);
+            proto.write(MemInfoDumpProto.AppData.SqlStats.PAGECACHE_OVERFLOW_KB,
+                    stats.pageCacheOverflow / 1024);
+            proto.write(MemInfoDumpProto.AppData.SqlStats.MALLOC_SIZE_KB,
+                    stats.largestMemAlloc / 1024);
+            int n = stats.dbStats.size();
+            for (int i = 0; i < n; i++) {
+                DbStats dbStats = stats.dbStats.get(i);
+
+                final long dToken = proto.start(MemInfoDumpProto.AppData.SqlStats.DATABASES);
+                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.NAME, dbStats.dbName);
+                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.PAGE_SIZE, dbStats.pageSize);
+                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.DB_SIZE, dbStats.dbSize);
+                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.LOOKASIDE_B,
+                        dbStats.lookaside);
+                proto.write(MemInfoDumpProto.AppData.SqlStats.Database.CACHE, dbStats.cache);
+                proto.end(dToken);
+            }
+            proto.end(sToken);
+
+            // Asset details.
+            String assetAlloc = AssetManager.getAssetAllocations();
+            if (assetAlloc != null) {
+                proto.write(MemInfoDumpProto.AppData.ASSET_ALLOCATIONS, assetAlloc);
+            }
+
+            // Unreachable native memory
+            if (dumpUnreachable) {
+                int flags = mBoundApplication == null ? 0 : mBoundApplication.appInfo.flags;
+                boolean showContents = (flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0
+                        || android.os.Build.IS_DEBUGGABLE;
+                proto.write(MemInfoDumpProto.AppData.UNREACHABLE_MEMORY,
+                        Debug.getUnreachableMemory(100, showContents));
+            }
+        }
+
+        @Override
+        public void dumpGfxInfo(ParcelFileDescriptor pfd, String[] args) {
+            nDumpGraphicsInfo(pfd.getFileDescriptor());
+            WindowManagerGlobal.getInstance().dumpGfxInfo(pfd.getFileDescriptor(), args);
+            IoUtils.closeQuietly(pfd);
+        }
+
+        private File getDatabasesDir(Context context) {
+            // There's no simple way to get the databases/ path, so do it this way.
+            return context.getDatabasePath("a").getParentFile();
+        }
+
+        private void dumpDatabaseInfo(ParcelFileDescriptor pfd, String[] args, boolean isSystem) {
+            PrintWriter pw = new FastPrintWriter(
+                    new FileOutputStream(pfd.getFileDescriptor()));
+            PrintWriterPrinter printer = new PrintWriterPrinter(pw);
+            SQLiteDebug.dump(printer, args, isSystem);
+            pw.flush();
+        }
+
+        @Override
+        public void dumpDbInfo(final ParcelFileDescriptor pfd, final String[] args) {
+            if (mSystemThread) {
+                // Ensure this invocation is asynchronous to prevent writer waiting if buffer cannot
+                // be consumed. But it must duplicate the file descriptor first, since caller might
+                // be closing it.
+                final ParcelFileDescriptor dup;
+                try {
+                    dup = pfd.dup();
+                } catch (IOException e) {
+                    Log.w(TAG, "Could not dup FD " + pfd.getFileDescriptor().getInt$());
+                    return;
+                } finally {
+                    IoUtils.closeQuietly(pfd);
+                }
+
+                AsyncTask.THREAD_POOL_EXECUTOR.execute(new Runnable() {
+                    @Override
+                    public void run() {
+                        try {
+                            dumpDatabaseInfo(dup, args, true);
+                        } finally {
+                            IoUtils.closeQuietly(dup);
+                        }
+                    }
+                });
+            } else {
+                dumpDatabaseInfo(pfd, args, false);
+                IoUtils.closeQuietly(pfd);
+            }
+        }
+
+        @Override
+        public void unstableProviderDied(IBinder provider) {
+            sendMessage(H.UNSTABLE_PROVIDER_DIED, provider);
+        }
+
+        @Override
+        public void requestAssistContextExtras(IBinder activityToken, IBinder requestToken,
+                int requestType, int sessionId, int flags) {
+            RequestAssistContextExtras cmd = new RequestAssistContextExtras();
+            cmd.activityToken = activityToken;
+            cmd.requestToken = requestToken;
+            cmd.requestType = requestType;
+            cmd.sessionId = sessionId;
+            cmd.flags = flags;
+            sendMessage(H.REQUEST_ASSIST_CONTEXT_EXTRAS, cmd);
+        }
+
+        public void setCoreSettings(Bundle coreSettings) {
+            sendMessage(H.SET_CORE_SETTINGS, coreSettings);
+        }
+
+        public void updatePackageCompatibilityInfo(String pkg, CompatibilityInfo info) {
+            UpdateCompatibilityData ucd = new UpdateCompatibilityData();
+            ucd.pkg = pkg;
+            ucd.info = info;
+            sendMessage(H.UPDATE_PACKAGE_COMPATIBILITY_INFO, ucd);
+        }
+
+        public void scheduleTrimMemory(int level) {
+            final Runnable r = PooledLambda.obtainRunnable(ActivityThread::handleTrimMemory,
+                    ActivityThread.this, level).recycleOnUse();
+            // Schedule trimming memory after drawing the frame to minimize jank-risk.
+            Choreographer choreographer = Choreographer.getMainThreadInstance();
+            if (choreographer != null) {
+                choreographer.postCallback(Choreographer.CALLBACK_COMMIT, r, null);
+            } else {
+                mH.post(r);
+            }
+        }
+
+        public void scheduleTranslucentConversionComplete(IBinder token, boolean drawComplete) {
+            sendMessage(H.TRANSLUCENT_CONVERSION_COMPLETE, token, drawComplete ? 1 : 0);
+        }
+
+        public void scheduleOnNewActivityOptions(IBinder token, Bundle options) {
+            sendMessage(H.ON_NEW_ACTIVITY_OPTIONS,
+                    new Pair<IBinder, ActivityOptions>(token, ActivityOptions.fromBundle(options)));
+        }
+
+        public void setProcessState(int state) {
+            updateProcessState(state, true);
+        }
+
+        /**
+         * Updates {@link #mNetworkBlockSeq}. This is used by ActivityManagerService to inform
+         * the main thread that it needs to wait for the network rules to get updated before
+         * launching an activity.
+         */
+        @Override
+        public void setNetworkBlockSeq(long procStateSeq) {
+            synchronized (mNetworkPolicyLock) {
+                mNetworkBlockSeq = procStateSeq;
+            }
+        }
+
+        @Override
+        public void scheduleInstallProvider(ProviderInfo provider) {
+            sendMessage(H.INSTALL_PROVIDER, provider);
+        }
+
+        @Override
+        public final void updateTimePrefs(int timeFormatPreference) {
+            final Boolean timeFormatPreferenceBool;
+            // For convenience we are using the Intent extra values.
+            if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_12_HOUR) {
+                timeFormatPreferenceBool = Boolean.FALSE;
+            } else if (timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_24_HOUR) {
+                timeFormatPreferenceBool = Boolean.TRUE;
+            } else {
+                // timeFormatPreference == Intent.EXTRA_TIME_PREF_VALUE_USE_LOCALE_DEFAULT
+                // (or unknown).
+                timeFormatPreferenceBool = null;
+            }
+            DateFormat.set24HourTimePref(timeFormatPreferenceBool);
+        }
+
+        @Override
+        public void scheduleEnterAnimationComplete(IBinder token) {
+            sendMessage(H.ENTER_ANIMATION_COMPLETE, token);
+        }
+
+        @Override
+        public void notifyCleartextNetwork(byte[] firstPacket) {
+            if (StrictMode.vmCleartextNetworkEnabled()) {
+                StrictMode.onCleartextNetworkDetected(firstPacket);
+            }
+        }
+
+        @Override
+        public void startBinderTracking() {
+            sendMessage(H.START_BINDER_TRACKING, null);
+        }
+
+        @Override
+        public void stopBinderTrackingAndDump(ParcelFileDescriptor pfd) {
+            try {
+                sendMessage(H.STOP_BINDER_TRACKING_AND_DUMP, pfd.dup());
+            } catch (IOException e) {
+            } finally {
+                IoUtils.closeQuietly(pfd);
+            }
+        }
+
+        @Override
+        public void scheduleLocalVoiceInteractionStarted(IBinder token,
+                IVoiceInteractor voiceInteractor) throws RemoteException {
+            SomeArgs args = SomeArgs.obtain();
+            args.arg1 = token;
+            args.arg2 = voiceInteractor;
+            sendMessage(H.LOCAL_VOICE_INTERACTION_STARTED, args);
+        }
+
+        @Override
+        public void handleTrustStorageUpdate() {
+            NetworkSecurityPolicy.getInstance().handleTrustStorageUpdate();
+        }
+
+        @Override
+        public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
+            ActivityThread.this.scheduleTransaction(transaction);
+        }
+
+        @Override
+        public void requestDirectActions(@NonNull IBinder activityToken,
+                @NonNull IVoiceInteractor interactor, @Nullable RemoteCallback cancellationCallback,
+                @NonNull RemoteCallback callback) {
+            final CancellationSignal cancellationSignal = new CancellationSignal();
+            if (cancellationCallback != null) {
+                final ICancellationSignal transport = createSafeCancellationTransport(
+                        cancellationSignal);
+                final Bundle cancellationResult = new Bundle();
+                cancellationResult.putBinder(VoiceInteractor.KEY_CANCELLATION_SIGNAL,
+                        transport.asBinder());
+                cancellationCallback.sendResult(cancellationResult);
+            }
+            mH.sendMessage(PooledLambda.obtainMessage(ActivityThread::handleRequestDirectActions,
+                    ActivityThread.this, activityToken, interactor, cancellationSignal, callback));
+        }
+
+        @Override
+        public void performDirectAction(@NonNull IBinder activityToken, @NonNull String actionId,
+                @Nullable Bundle arguments, @Nullable RemoteCallback cancellationCallback,
+                @NonNull RemoteCallback resultCallback) {
+            final CancellationSignal cancellationSignal = new CancellationSignal();
+            if (cancellationCallback != null) {
+                final ICancellationSignal transport = createSafeCancellationTransport(
+                        cancellationSignal);
+                final Bundle cancellationResult = new Bundle();
+                cancellationResult.putBinder(VoiceInteractor.KEY_CANCELLATION_SIGNAL,
+                        transport.asBinder());
+                cancellationCallback.sendResult(cancellationResult);
+            }
+            mH.sendMessage(PooledLambda.obtainMessage(ActivityThread::handlePerformDirectAction,
+                    ActivityThread.this, activityToken, actionId, arguments,
+                    cancellationSignal, resultCallback));
+        }
+    }
+
+    private @NonNull SafeCancellationTransport createSafeCancellationTransport(
+            @NonNull CancellationSignal cancellationSignal) {
+        synchronized (ActivityThread.this) {
+            if (mRemoteCancellations == null) {
+                mRemoteCancellations = new ArrayMap<>();
+            }
+            final SafeCancellationTransport transport = new SafeCancellationTransport(
+                    this, cancellationSignal);
+            mRemoteCancellations.put(transport, cancellationSignal);
+            return transport;
+        }
+    }
+
+    private @NonNull CancellationSignal removeSafeCancellationTransport(
+            @NonNull SafeCancellationTransport transport) {
+        synchronized (ActivityThread.this) {
+            final CancellationSignal cancellation = mRemoteCancellations.remove(transport);
+            if (mRemoteCancellations.isEmpty()) {
+                mRemoteCancellations = null;
+            }
+            return cancellation;
+        }
+    }
+
+    private static final class SafeCancellationTransport extends ICancellationSignal.Stub {
+        private final @NonNull WeakReference<ActivityThread> mWeakActivityThread;
+
+        SafeCancellationTransport(@NonNull ActivityThread activityThread,
+                @NonNull CancellationSignal cancellation) {
+            mWeakActivityThread = new WeakReference<>(activityThread);
+        }
+
+        @Override
+        public void cancel() {
+            final ActivityThread activityThread = mWeakActivityThread.get();
+            if (activityThread != null) {
+                final CancellationSignal cancellation = activityThread
+                        .removeSafeCancellationTransport(this);
+                if (cancellation != null) {
+                    cancellation.cancel();
+                }
+            }
+        }
+    }
+
+    class H extends Handler {
+        public static final int BIND_APPLICATION        = 110;
+        @UnsupportedAppUsage
+        public static final int EXIT_APPLICATION        = 111;
+        @UnsupportedAppUsage
+        public static final int RECEIVER                = 113;
+        @UnsupportedAppUsage
+        public static final int CREATE_SERVICE          = 114;
+        @UnsupportedAppUsage
+        public static final int SERVICE_ARGS            = 115;
+        @UnsupportedAppUsage
+        public static final int STOP_SERVICE            = 116;
+
+        public static final int CONFIGURATION_CHANGED   = 118;
+        public static final int CLEAN_UP_CONTEXT        = 119;
+        @UnsupportedAppUsage
+        public static final int GC_WHEN_IDLE            = 120;
+        @UnsupportedAppUsage
+        public static final int BIND_SERVICE            = 121;
+        @UnsupportedAppUsage
+        public static final int UNBIND_SERVICE          = 122;
+        public static final int DUMP_SERVICE            = 123;
+        public static final int LOW_MEMORY              = 124;
+        public static final int PROFILER_CONTROL        = 127;
+        public static final int CREATE_BACKUP_AGENT     = 128;
+        public static final int DESTROY_BACKUP_AGENT    = 129;
+        public static final int SUICIDE                 = 130;
+        @UnsupportedAppUsage
+        public static final int REMOVE_PROVIDER         = 131;
+        public static final int DISPATCH_PACKAGE_BROADCAST = 133;
+        @UnsupportedAppUsage
+        public static final int SCHEDULE_CRASH          = 134;
+        public static final int DUMP_HEAP               = 135;
+        public static final int DUMP_ACTIVITY           = 136;
+        public static final int SLEEPING                = 137;
+        public static final int SET_CORE_SETTINGS       = 138;
+        public static final int UPDATE_PACKAGE_COMPATIBILITY_INFO = 139;
+        @UnsupportedAppUsage
+        public static final int DUMP_PROVIDER           = 141;
+        public static final int UNSTABLE_PROVIDER_DIED  = 142;
+        public static final int REQUEST_ASSIST_CONTEXT_EXTRAS = 143;
+        public static final int TRANSLUCENT_CONVERSION_COMPLETE = 144;
+        @UnsupportedAppUsage
+        public static final int INSTALL_PROVIDER        = 145;
+        public static final int ON_NEW_ACTIVITY_OPTIONS = 146;
+        @UnsupportedAppUsage
+        public static final int ENTER_ANIMATION_COMPLETE = 149;
+        public static final int START_BINDER_TRACKING = 150;
+        public static final int STOP_BINDER_TRACKING_AND_DUMP = 151;
+        public static final int LOCAL_VOICE_INTERACTION_STARTED = 154;
+        public static final int ATTACH_AGENT = 155;
+        public static final int APPLICATION_INFO_CHANGED = 156;
+        public static final int RUN_ISOLATED_ENTRY_POINT = 158;
+        public static final int EXECUTE_TRANSACTION = 159;
+        public static final int RELAUNCH_ACTIVITY = 160;
+        public static final int PURGE_RESOURCES = 161;
+
+        String codeToString(int code) {
+            if (DEBUG_MESSAGES) {
+                switch (code) {
+                    case BIND_APPLICATION: return "BIND_APPLICATION";
+                    case EXIT_APPLICATION: return "EXIT_APPLICATION";
+                    case RECEIVER: return "RECEIVER";
+                    case CREATE_SERVICE: return "CREATE_SERVICE";
+                    case SERVICE_ARGS: return "SERVICE_ARGS";
+                    case STOP_SERVICE: return "STOP_SERVICE";
+                    case CONFIGURATION_CHANGED: return "CONFIGURATION_CHANGED";
+                    case CLEAN_UP_CONTEXT: return "CLEAN_UP_CONTEXT";
+                    case GC_WHEN_IDLE: return "GC_WHEN_IDLE";
+                    case BIND_SERVICE: return "BIND_SERVICE";
+                    case UNBIND_SERVICE: return "UNBIND_SERVICE";
+                    case DUMP_SERVICE: return "DUMP_SERVICE";
+                    case LOW_MEMORY: return "LOW_MEMORY";
+                    case PROFILER_CONTROL: return "PROFILER_CONTROL";
+                    case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT";
+                    case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT";
+                    case SUICIDE: return "SUICIDE";
+                    case REMOVE_PROVIDER: return "REMOVE_PROVIDER";
+                    case DISPATCH_PACKAGE_BROADCAST: return "DISPATCH_PACKAGE_BROADCAST";
+                    case SCHEDULE_CRASH: return "SCHEDULE_CRASH";
+                    case DUMP_HEAP: return "DUMP_HEAP";
+                    case DUMP_ACTIVITY: return "DUMP_ACTIVITY";
+                    case SLEEPING: return "SLEEPING";
+                    case SET_CORE_SETTINGS: return "SET_CORE_SETTINGS";
+                    case UPDATE_PACKAGE_COMPATIBILITY_INFO: return "UPDATE_PACKAGE_COMPATIBILITY_INFO";
+                    case DUMP_PROVIDER: return "DUMP_PROVIDER";
+                    case UNSTABLE_PROVIDER_DIED: return "UNSTABLE_PROVIDER_DIED";
+                    case REQUEST_ASSIST_CONTEXT_EXTRAS: return "REQUEST_ASSIST_CONTEXT_EXTRAS";
+                    case TRANSLUCENT_CONVERSION_COMPLETE: return "TRANSLUCENT_CONVERSION_COMPLETE";
+                    case INSTALL_PROVIDER: return "INSTALL_PROVIDER";
+                    case ON_NEW_ACTIVITY_OPTIONS: return "ON_NEW_ACTIVITY_OPTIONS";
+                    case ENTER_ANIMATION_COMPLETE: return "ENTER_ANIMATION_COMPLETE";
+                    case LOCAL_VOICE_INTERACTION_STARTED: return "LOCAL_VOICE_INTERACTION_STARTED";
+                    case ATTACH_AGENT: return "ATTACH_AGENT";
+                    case APPLICATION_INFO_CHANGED: return "APPLICATION_INFO_CHANGED";
+                    case RUN_ISOLATED_ENTRY_POINT: return "RUN_ISOLATED_ENTRY_POINT";
+                    case EXECUTE_TRANSACTION: return "EXECUTE_TRANSACTION";
+                    case RELAUNCH_ACTIVITY: return "RELAUNCH_ACTIVITY";
+                    case PURGE_RESOURCES: return "PURGE_RESOURCES";
+                }
+            }
+            return Integer.toString(code);
+        }
+        public void handleMessage(Message msg) {
+            if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
+            switch (msg.what) {
+                case BIND_APPLICATION:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
+                    AppBindData data = (AppBindData)msg.obj;
+                    handleBindApplication(data);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case EXIT_APPLICATION:
+                    if (mInitialApplication != null) {
+                        mInitialApplication.onTerminate();
+                    }
+                    Looper.myLooper().quit();
+                    break;
+                case RECEIVER:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastReceiveComp");
+                    handleReceiver((ReceiverData)msg.obj);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case CREATE_SERVICE:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceCreate: " + String.valueOf(msg.obj)));
+                    handleCreateService((CreateServiceData)msg.obj);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case BIND_SERVICE:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceBind");
+                    handleBindService((BindServiceData)msg.obj);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case UNBIND_SERVICE:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceUnbind");
+                    handleUnbindService((BindServiceData)msg.obj);
+                    schedulePurgeIdler();
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case SERVICE_ARGS:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, ("serviceStart: " + String.valueOf(msg.obj)));
+                    handleServiceArgs((ServiceArgsData)msg.obj);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case STOP_SERVICE:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "serviceStop");
+                    handleStopService((IBinder)msg.obj);
+                    schedulePurgeIdler();
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case CONFIGURATION_CHANGED:
+                    handleConfigurationChanged((Configuration) msg.obj);
+                    break;
+                case CLEAN_UP_CONTEXT:
+                    ContextCleanupInfo cci = (ContextCleanupInfo)msg.obj;
+                    cci.context.performFinalCleanup(cci.who, cci.what);
+                    break;
+                case GC_WHEN_IDLE:
+                    scheduleGcIdler();
+                    break;
+                case DUMP_SERVICE:
+                    handleDumpService((DumpComponentInfo)msg.obj);
+                    break;
+                case LOW_MEMORY:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "lowMemory");
+                    handleLowMemory();
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case PROFILER_CONTROL:
+                    handleProfilerControl(msg.arg1 != 0, (ProfilerInfo)msg.obj, msg.arg2);
+                    break;
+                case CREATE_BACKUP_AGENT:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupCreateAgent");
+                    handleCreateBackupAgent((CreateBackupAgentData)msg.obj);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case DESTROY_BACKUP_AGENT:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "backupDestroyAgent");
+                    handleDestroyBackupAgent((CreateBackupAgentData)msg.obj);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case SUICIDE:
+                    Process.killProcess(Process.myPid());
+                    break;
+                case REMOVE_PROVIDER:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "providerRemove");
+                    completeRemoveProvider((ProviderRefCount)msg.obj);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case DISPATCH_PACKAGE_BROADCAST:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "broadcastPackage");
+                    handleDispatchPackageBroadcast(msg.arg1, (String[])msg.obj);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case SCHEDULE_CRASH:
+                    throw new RemoteServiceException((String)msg.obj);
+                case DUMP_HEAP:
+                    handleDumpHeap((DumpHeapData) msg.obj);
+                    break;
+                case DUMP_ACTIVITY:
+                    handleDumpActivity((DumpComponentInfo)msg.obj);
+                    break;
+                case DUMP_PROVIDER:
+                    handleDumpProvider((DumpComponentInfo)msg.obj);
+                    break;
+                case SLEEPING:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "sleeping");
+                    handleSleeping((IBinder)msg.obj, msg.arg1 != 0);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case SET_CORE_SETTINGS:
+                    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setCoreSettings");
+                    handleSetCoreSettings((Bundle) msg.obj);
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    break;
+                case UPDATE_PACKAGE_COMPATIBILITY_INFO:
+                    handleUpdatePackageCompatibilityInfo((UpdateCompatibilityData)msg.obj);
+                    break;
+                case UNSTABLE_PROVIDER_DIED:
+                    handleUnstableProviderDied((IBinder)msg.obj, false);
+                    break;
+                case REQUEST_ASSIST_CONTEXT_EXTRAS:
+                    handleRequestAssistContextExtras((RequestAssistContextExtras)msg.obj);
+                    break;
+                case TRANSLUCENT_CONVERSION_COMPLETE:
+                    handleTranslucentConversionComplete((IBinder)msg.obj, msg.arg1 == 1);
+                    break;
+                case INSTALL_PROVIDER:
+                    handleInstallProvider((ProviderInfo) msg.obj);
+                    break;
+                case ON_NEW_ACTIVITY_OPTIONS:
+                    Pair<IBinder, ActivityOptions> pair = (Pair<IBinder, ActivityOptions>) msg.obj;
+                    onNewActivityOptions(pair.first, pair.second);
+                    break;
+                case ENTER_ANIMATION_COMPLETE:
+                    handleEnterAnimationComplete((IBinder) msg.obj);
+                    break;
+                case START_BINDER_TRACKING:
+                    handleStartBinderTracking();
+                    break;
+                case STOP_BINDER_TRACKING_AND_DUMP:
+                    handleStopBinderTrackingAndDump((ParcelFileDescriptor) msg.obj);
+                    break;
+                case LOCAL_VOICE_INTERACTION_STARTED:
+                    handleLocalVoiceInteractionStarted((IBinder) ((SomeArgs) msg.obj).arg1,
+                            (IVoiceInteractor) ((SomeArgs) msg.obj).arg2);
+                    break;
+                case ATTACH_AGENT: {
+                    Application app = getApplication();
+                    handleAttachAgent((String) msg.obj, app != null ? app.mLoadedApk : null);
+                    break;
+                }
+                case APPLICATION_INFO_CHANGED:
+                    mUpdatingSystemConfig = true;
+                    try {
+                        handleApplicationInfoChanged((ApplicationInfo) msg.obj);
+                    } finally {
+                        mUpdatingSystemConfig = false;
+                    }
+                    break;
+                case RUN_ISOLATED_ENTRY_POINT:
+                    handleRunIsolatedEntryPoint((String) ((SomeArgs) msg.obj).arg1,
+                            (String[]) ((SomeArgs) msg.obj).arg2);
+                    break;
+                case EXECUTE_TRANSACTION:
+                    final ClientTransaction transaction = (ClientTransaction) msg.obj;
+                    mTransactionExecutor.execute(transaction);
+                    if (isSystem()) {
+                        // Client transactions inside system process are recycled on the client side
+                        // instead of ClientLifecycleManager to avoid being cleared before this
+                        // message is handled.
+                        transaction.recycle();
+                    }
+                    // TODO(lifecycler): Recycle locally scheduled transactions.
+                    break;
+                case RELAUNCH_ACTIVITY:
+                    handleRelaunchActivityLocally((IBinder) msg.obj);
+                    break;
+                case PURGE_RESOURCES:
+                    schedulePurgeIdler();
+                    break;
+            }
+            Object obj = msg.obj;
+            if (obj instanceof SomeArgs) {
+                ((SomeArgs) obj).recycle();
+            }
+            if (DEBUG_MESSAGES) Slog.v(TAG, "<<< done: " + codeToString(msg.what));
+        }
+    }
+
+    private class Idler implements MessageQueue.IdleHandler {
+        @Override
+        public final boolean queueIdle() {
+            ActivityClientRecord a = mNewActivities;
+            boolean stopProfiling = false;
+            if (mBoundApplication != null && mProfiler.profileFd != null
+                    && mProfiler.autoStopProfiler) {
+                stopProfiling = true;
+            }
+            if (a != null) {
+                mNewActivities = null;
+                IActivityTaskManager am = ActivityTaskManager.getService();
+                ActivityClientRecord prev;
+                do {
+                    if (localLOGV) Slog.v(
+                        TAG, "Reporting idle of " + a +
+                        " finished=" +
+                        (a.activity != null && a.activity.mFinished));
+                    if (a.activity != null && !a.activity.mFinished) {
+                        try {
+                            am.activityIdle(a.token, a.createdConfig, stopProfiling);
+                            a.createdConfig = null;
+                        } catch (RemoteException ex) {
+                            throw ex.rethrowFromSystemServer();
+                        }
+                    }
+                    prev = a;
+                    a = a.nextIdle;
+                    prev.nextIdle = null;
+                } while (a != null);
+            }
+            if (stopProfiling) {
+                mProfiler.stopProfiling();
+            }
+            applyPendingProcessState();
+            return false;
+        }
+    }
+
+    final class GcIdler implements MessageQueue.IdleHandler {
+        @Override
+        public final boolean queueIdle() {
+            doGcIfNeeded();
+            purgePendingResources();
+            return false;
+        }
+    }
+
+    final class PurgeIdler implements MessageQueue.IdleHandler {
+        @Override
+        public boolean queueIdle() {
+            purgePendingResources();
+            return false;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public static ActivityThread currentActivityThread() {
+        return sCurrentActivityThread;
+    }
+
+    public static boolean isSystem() {
+        return (sCurrentActivityThread != null) ? sCurrentActivityThread.mSystemThread : false;
+    }
+
+    public static String currentOpPackageName() {
+        ActivityThread am = currentActivityThread();
+        return (am != null && am.getApplication() != null)
+                ? am.getApplication().getOpPackageName() : null;
+    }
+
+    @UnsupportedAppUsage
+    public static String currentPackageName() {
+        ActivityThread am = currentActivityThread();
+        return (am != null && am.mBoundApplication != null)
+            ? am.mBoundApplication.appInfo.packageName : null;
+    }
+
+    @UnsupportedAppUsage
+    public static String currentProcessName() {
+        ActivityThread am = currentActivityThread();
+        return (am != null && am.mBoundApplication != null)
+            ? am.mBoundApplication.processName : null;
+    }
+
+    @UnsupportedAppUsage
+    public static Application currentApplication() {
+        ActivityThread am = currentActivityThread();
+        return am != null ? am.mInitialApplication : null;
+    }
+
+    @UnsupportedAppUsage
+    public static IPackageManager getPackageManager() {
+        if (sPackageManager != null) {
+            //Slog.v("PackageManager", "returning cur default = " + sPackageManager);
+            return sPackageManager;
+        }
+        IBinder b = ServiceManager.getService("package");
+        //Slog.v("PackageManager", "default service binder = " + b);
+        sPackageManager = IPackageManager.Stub.asInterface(b);
+        //Slog.v("PackageManager", "default service = " + sPackageManager);
+        return sPackageManager;
+    }
+
+    private Configuration mMainThreadConfig = new Configuration();
+
+    Configuration applyConfigCompatMainThread(int displayDensity, Configuration config,
+            CompatibilityInfo compat) {
+        if (config == null) {
+            return null;
+        }
+        if (!compat.supportsScreen()) {
+            mMainThreadConfig.setTo(config);
+            config = mMainThreadConfig;
+            compat.applyToConfiguration(displayDensity, config);
+        }
+        return config;
+    }
+
+    /**
+     * Creates the top level resources for the given package. Will return an existing
+     * Resources if one has already been created.
+     */
+    Resources getTopLevelResources(String resDir, String[] splitResDirs, String[] overlayDirs,
+            String[] libDirs, int displayId, LoadedApk pkgInfo) {
+        return mResourcesManager.getResources(null, resDir, splitResDirs, overlayDirs, libDirs,
+                displayId, null, pkgInfo.getCompatibilityInfo(), pkgInfo.getClassLoader());
+    }
+
+    @UnsupportedAppUsage
+    final Handler getHandler() {
+        return mH;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
+            int flags) {
+        return getPackageInfo(packageName, compatInfo, flags, UserHandle.myUserId());
+    }
+
+    public final LoadedApk getPackageInfo(String packageName, CompatibilityInfo compatInfo,
+            int flags, int userId) {
+        final boolean differentUser = (UserHandle.myUserId() != userId);
+        ApplicationInfo ai;
+        try {
+            ai = getPackageManager().getApplicationInfo(packageName,
+                    PackageManager.GET_SHARED_LIBRARY_FILES
+                            | PackageManager.MATCH_DEBUG_TRIAGED_MISSING,
+                    (userId < 0) ? UserHandle.myUserId() : userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        synchronized (mResourcesManager) {
+            WeakReference<LoadedApk> ref;
+            if (differentUser) {
+                // Caching not supported across users
+                ref = null;
+            } else if ((flags & Context.CONTEXT_INCLUDE_CODE) != 0) {
+                ref = mPackages.get(packageName);
+            } else {
+                ref = mResourcePackages.get(packageName);
+            }
+
+            LoadedApk packageInfo = ref != null ? ref.get() : null;
+            if (ai != null && packageInfo != null) {
+                if (!isLoadedApkResourceDirsUpToDate(packageInfo, ai)) {
+                    packageInfo.updateApplicationInfo(ai, null);
+                }
+
+                if (packageInfo.isSecurityViolation()
+                        && (flags&Context.CONTEXT_IGNORE_SECURITY) == 0) {
+                    throw new SecurityException(
+                            "Requesting code from " + packageName
+                            + " to be run in process "
+                            + mBoundApplication.processName
+                            + "/" + mBoundApplication.appInfo.uid);
+                }
+                return packageInfo;
+            }
+        }
+
+        if (ai != null) {
+            return getPackageInfo(ai, compatInfo, flags);
+        }
+
+        return null;
+    }
+
+    @UnsupportedAppUsage
+    public final LoadedApk getPackageInfo(ApplicationInfo ai, CompatibilityInfo compatInfo,
+            int flags) {
+        boolean includeCode = (flags&Context.CONTEXT_INCLUDE_CODE) != 0;
+        boolean securityViolation = includeCode && ai.uid != 0
+                && ai.uid != Process.SYSTEM_UID && (mBoundApplication != null
+                        ? !UserHandle.isSameApp(ai.uid, mBoundApplication.appInfo.uid)
+                        : true);
+        boolean registerPackage = includeCode && (flags&Context.CONTEXT_REGISTER_PACKAGE) != 0;
+        if ((flags&(Context.CONTEXT_INCLUDE_CODE
+                |Context.CONTEXT_IGNORE_SECURITY))
+                == Context.CONTEXT_INCLUDE_CODE) {
+            if (securityViolation) {
+                String msg = "Requesting code from " + ai.packageName
+                        + " (with uid " + ai.uid + ")";
+                if (mBoundApplication != null) {
+                    msg = msg + " to be run in process "
+                        + mBoundApplication.processName + " (with uid "
+                        + mBoundApplication.appInfo.uid + ")";
+                }
+                throw new SecurityException(msg);
+            }
+        }
+        return getPackageInfo(ai, compatInfo, null, securityViolation, includeCode,
+                registerPackage);
+    }
+
+    @Override
+    @UnsupportedAppUsage
+    public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,
+            CompatibilityInfo compatInfo) {
+        return getPackageInfo(ai, compatInfo, null, false, true, false);
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public final LoadedApk peekPackageInfo(String packageName, boolean includeCode) {
+        synchronized (mResourcesManager) {
+            WeakReference<LoadedApk> ref;
+            if (includeCode) {
+                ref = mPackages.get(packageName);
+            } else {
+                ref = mResourcePackages.get(packageName);
+            }
+            return ref != null ? ref.get() : null;
+        }
+    }
+
+    private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,
+            ClassLoader baseLoader, boolean securityViolation, boolean includeCode,
+            boolean registerPackage) {
+        final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
+        synchronized (mResourcesManager) {
+            WeakReference<LoadedApk> ref;
+            if (differentUser) {
+                // Caching not supported across users
+                ref = null;
+            } else if (includeCode) {
+                ref = mPackages.get(aInfo.packageName);
+            } else {
+                ref = mResourcePackages.get(aInfo.packageName);
+            }
+
+            LoadedApk packageInfo = ref != null ? ref.get() : null;
+
+            if (packageInfo != null) {
+                if (!isLoadedApkResourceDirsUpToDate(packageInfo, aInfo)) {
+                    packageInfo.updateApplicationInfo(aInfo, null);
+                }
+
+                return packageInfo;
+            }
+
+            if (localLOGV) {
+                Slog.v(TAG, (includeCode ? "Loading code package "
+                        : "Loading resource-only package ") + aInfo.packageName
+                        + " (in " + (mBoundApplication != null
+                        ? mBoundApplication.processName : null)
+                        + ")");
+            }
+
+            packageInfo =
+                    new LoadedApk(this, aInfo, compatInfo, baseLoader,
+                            securityViolation, includeCode
+                            && (aInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
+
+            if (mSystemThread && "android".equals(aInfo.packageName)) {
+                packageInfo.installSystemApplicationInfo(aInfo,
+                        getSystemContext().mPackageInfo.getClassLoader());
+            }
+
+            if (differentUser) {
+                // Caching not supported across users
+            } else if (includeCode) {
+                mPackages.put(aInfo.packageName,
+                        new WeakReference<LoadedApk>(packageInfo));
+            } else {
+                mResourcePackages.put(aInfo.packageName,
+                        new WeakReference<LoadedApk>(packageInfo));
+            }
+
+            return packageInfo;
+        }
+    }
+
+    private static boolean isLoadedApkResourceDirsUpToDate(LoadedApk loadedApk,
+            ApplicationInfo appInfo) {
+        Resources packageResources = loadedApk.mResources;
+        String[] overlayDirs = ArrayUtils.defeatNullable(loadedApk.getOverlayDirs());
+        String[] resourceDirs = ArrayUtils.defeatNullable(appInfo.resourceDirs);
+
+        return (packageResources == null || packageResources.getAssets().isUpToDate())
+                && overlayDirs.length == resourceDirs.length
+                && ArrayUtils.containsAll(overlayDirs, resourceDirs);
+    }
+
+    @UnsupportedAppUsage
+    ActivityThread() {
+        mResourcesManager = ResourcesManager.getInstance();
+    }
+
+    @UnsupportedAppUsage
+    public ApplicationThread getApplicationThread()
+    {
+        return mAppThread;
+    }
+
+    @UnsupportedAppUsage
+    public Instrumentation getInstrumentation()
+    {
+        return mInstrumentation;
+    }
+
+    public boolean isProfiling() {
+        return mProfiler != null && mProfiler.profileFile != null
+                && mProfiler.profileFd == null;
+    }
+
+    public String getProfileFilePath() {
+        return mProfiler.profileFile;
+    }
+
+    @UnsupportedAppUsage
+    public Looper getLooper() {
+        return mLooper;
+    }
+
+    public Executor getExecutor() {
+        return mExecutor;
+    }
+
+    @UnsupportedAppUsage
+    public Application getApplication() {
+        return mInitialApplication;
+    }
+
+    @UnsupportedAppUsage
+    public String getProcessName() {
+        return mBoundApplication.processName;
+    }
+
+    @UnsupportedAppUsage
+    public ContextImpl getSystemContext() {
+        synchronized (this) {
+            if (mSystemContext == null) {
+                mSystemContext = ContextImpl.createSystemContext(this);
+            }
+            return mSystemContext;
+        }
+    }
+
+    public ContextImpl getSystemUiContext() {
+        synchronized (this) {
+            if (mSystemUiContext == null) {
+                mSystemUiContext = ContextImpl.createSystemUiContext(getSystemContext());
+            }
+            return mSystemUiContext;
+        }
+    }
+
+    /**
+     * Create the context instance base on system resources & display information which used for UI.
+     * @param displayId The ID of the display where the UI is shown.
+     * @see ContextImpl#createSystemUiContext(ContextImpl, int)
+     */
+    public ContextImpl createSystemUiContext(int displayId) {
+        return ContextImpl.createSystemUiContext(getSystemUiContext(), displayId);
+    }
+
+    public void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {
+        synchronized (this) {
+            getSystemContext().installSystemApplicationInfo(info, classLoader);
+            getSystemUiContext().installSystemApplicationInfo(info, classLoader);
+
+            // give ourselves a default profiler
+            mProfiler = new Profiler();
+        }
+    }
+
+    @UnsupportedAppUsage
+    void scheduleGcIdler() {
+        if (!mGcIdlerScheduled) {
+            mGcIdlerScheduled = true;
+            Looper.myQueue().addIdleHandler(mGcIdler);
+        }
+        mH.removeMessages(H.GC_WHEN_IDLE);
+    }
+
+    void unscheduleGcIdler() {
+        if (mGcIdlerScheduled) {
+            mGcIdlerScheduled = false;
+            Looper.myQueue().removeIdleHandler(mGcIdler);
+        }
+        mH.removeMessages(H.GC_WHEN_IDLE);
+    }
+
+    void schedulePurgeIdler() {
+        if (!mPurgeIdlerScheduled) {
+            mPurgeIdlerScheduled = true;
+            Looper.myQueue().addIdleHandler(mPurgeIdler);
+        }
+        mH.removeMessages(H.PURGE_RESOURCES);
+    }
+
+    void unschedulePurgeIdler() {
+        if (mPurgeIdlerScheduled) {
+            mPurgeIdlerScheduled = false;
+            Looper.myQueue().removeIdleHandler(mPurgeIdler);
+        }
+        mH.removeMessages(H.PURGE_RESOURCES);
+    }
+
+    void doGcIfNeeded() {
+        doGcIfNeeded("bg");
+    }
+
+    void doGcIfNeeded(String reason) {
+        mGcIdlerScheduled = false;
+        final long now = SystemClock.uptimeMillis();
+        //Slog.i(TAG, "**** WE MIGHT WANT TO GC: then=" + Binder.getLastGcTime()
+        //        + "m now=" + now);
+        if ((BinderInternal.getLastGcTime()+MIN_TIME_BETWEEN_GCS) < now) {
+            //Slog.i(TAG, "**** WE DO, WE DO WANT TO GC!");
+            BinderInternal.forceGc(reason);
+        }
+    }
+
+    private static final String HEAP_FULL_COLUMN
+            = "%13s %8s %8s %8s %8s %8s %8s %8s %8s %8s %8s";
+    private static final String HEAP_COLUMN
+            = "%13s %8s %8s %8s %8s %8s %8s %8s";
+    private static final String ONE_COUNT_COLUMN = "%21s %8d";
+    private static final String TWO_COUNT_COLUMNS = "%21s %8d %21s %8d";
+    private static final String ONE_COUNT_COLUMN_HEADER = "%21s %8s";
+
+    // Formatting for checkin service - update version if row format changes
+    private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 4;
+
+    static void printRow(PrintWriter pw, String format, Object...objs) {
+        pw.println(String.format(format, objs));
+    }
+
+    public static void dumpMemInfoTable(PrintWriter pw, Debug.MemoryInfo memInfo, boolean checkin,
+            boolean dumpFullInfo, boolean dumpDalvik, boolean dumpSummaryOnly,
+            int pid, String processName,
+            long nativeMax, long nativeAllocated, long nativeFree,
+            long dalvikMax, long dalvikAllocated, long dalvikFree) {
+
+        // For checkin, we print one long comma-separated list of values
+        if (checkin) {
+            // NOTE: if you change anything significant below, also consider changing
+            // ACTIVITY_THREAD_CHECKIN_VERSION.
+
+            // Header
+            pw.print(ACTIVITY_THREAD_CHECKIN_VERSION); pw.print(',');
+            pw.print(pid); pw.print(',');
+            pw.print(processName); pw.print(',');
+
+            // Heap info - max
+            pw.print(nativeMax); pw.print(',');
+            pw.print(dalvikMax); pw.print(',');
+            pw.print("N/A,");
+            pw.print(nativeMax + dalvikMax); pw.print(',');
+
+            // Heap info - allocated
+            pw.print(nativeAllocated); pw.print(',');
+            pw.print(dalvikAllocated); pw.print(',');
+            pw.print("N/A,");
+            pw.print(nativeAllocated + dalvikAllocated); pw.print(',');
+
+            // Heap info - free
+            pw.print(nativeFree); pw.print(',');
+            pw.print(dalvikFree); pw.print(',');
+            pw.print("N/A,");
+            pw.print(nativeFree + dalvikFree); pw.print(',');
+
+            // Heap info - proportional set size
+            pw.print(memInfo.nativePss); pw.print(',');
+            pw.print(memInfo.dalvikPss); pw.print(',');
+            pw.print(memInfo.otherPss); pw.print(',');
+            pw.print(memInfo.getTotalPss()); pw.print(',');
+
+            // Heap info - swappable set size
+            pw.print(memInfo.nativeSwappablePss); pw.print(',');
+            pw.print(memInfo.dalvikSwappablePss); pw.print(',');
+            pw.print(memInfo.otherSwappablePss); pw.print(',');
+            pw.print(memInfo.getTotalSwappablePss()); pw.print(',');
+
+            // Heap info - shared dirty
+            pw.print(memInfo.nativeSharedDirty); pw.print(',');
+            pw.print(memInfo.dalvikSharedDirty); pw.print(',');
+            pw.print(memInfo.otherSharedDirty); pw.print(',');
+            pw.print(memInfo.getTotalSharedDirty()); pw.print(',');
+
+            // Heap info - shared clean
+            pw.print(memInfo.nativeSharedClean); pw.print(',');
+            pw.print(memInfo.dalvikSharedClean); pw.print(',');
+            pw.print(memInfo.otherSharedClean); pw.print(',');
+            pw.print(memInfo.getTotalSharedClean()); pw.print(',');
+
+            // Heap info - private Dirty
+            pw.print(memInfo.nativePrivateDirty); pw.print(',');
+            pw.print(memInfo.dalvikPrivateDirty); pw.print(',');
+            pw.print(memInfo.otherPrivateDirty); pw.print(',');
+            pw.print(memInfo.getTotalPrivateDirty()); pw.print(',');
+
+            // Heap info - private Clean
+            pw.print(memInfo.nativePrivateClean); pw.print(',');
+            pw.print(memInfo.dalvikPrivateClean); pw.print(',');
+            pw.print(memInfo.otherPrivateClean); pw.print(',');
+            pw.print(memInfo.getTotalPrivateClean()); pw.print(',');
+
+            // Heap info - swapped out
+            pw.print(memInfo.nativeSwappedOut); pw.print(',');
+            pw.print(memInfo.dalvikSwappedOut); pw.print(',');
+            pw.print(memInfo.otherSwappedOut); pw.print(',');
+            pw.print(memInfo.getTotalSwappedOut()); pw.print(',');
+
+            // Heap info - swapped out pss
+            if (memInfo.hasSwappedOutPss) {
+                pw.print(memInfo.nativeSwappedOutPss); pw.print(',');
+                pw.print(memInfo.dalvikSwappedOutPss); pw.print(',');
+                pw.print(memInfo.otherSwappedOutPss); pw.print(',');
+                pw.print(memInfo.getTotalSwappedOutPss()); pw.print(',');
+            } else {
+                pw.print("N/A,");
+                pw.print("N/A,");
+                pw.print("N/A,");
+                pw.print("N/A,");
+            }
+
+            // Heap info - other areas
+            for (int i=0; i<Debug.MemoryInfo.NUM_OTHER_STATS; i++) {
+                pw.print(Debug.MemoryInfo.getOtherLabel(i)); pw.print(',');
+                pw.print(memInfo.getOtherPss(i)); pw.print(',');
+                pw.print(memInfo.getOtherSwappablePss(i)); pw.print(',');
+                pw.print(memInfo.getOtherSharedDirty(i)); pw.print(',');
+                pw.print(memInfo.getOtherSharedClean(i)); pw.print(',');
+                pw.print(memInfo.getOtherPrivateDirty(i)); pw.print(',');
+                pw.print(memInfo.getOtherPrivateClean(i)); pw.print(',');
+                pw.print(memInfo.getOtherSwappedOut(i)); pw.print(',');
+                if (memInfo.hasSwappedOutPss) {
+                    pw.print(memInfo.getOtherSwappedOutPss(i)); pw.print(',');
+                } else {
+                    pw.print("N/A,");
+                }
+            }
+            return;
+        }
+
+        if (!dumpSummaryOnly) {
+            if (dumpFullInfo) {
+                printRow(pw, HEAP_FULL_COLUMN, "", "Pss", "Pss", "Shared", "Private",
+                        "Shared", "Private", memInfo.hasSwappedOutPss ? "SwapPss" : "Swap",
+                        "Heap", "Heap", "Heap");
+                printRow(pw, HEAP_FULL_COLUMN, "", "Total", "Clean", "Dirty", "Dirty",
+                        "Clean", "Clean", "Dirty",
+                        "Size", "Alloc", "Free");
+                printRow(pw, HEAP_FULL_COLUMN, "", "------", "------", "------", "------",
+                        "------", "------", "------", "------", "------", "------");
+                printRow(pw, HEAP_FULL_COLUMN, "Native Heap", memInfo.nativePss,
+                        memInfo.nativeSwappablePss, memInfo.nativeSharedDirty,
+                        memInfo.nativePrivateDirty, memInfo.nativeSharedClean,
+                        memInfo.nativePrivateClean, memInfo.hasSwappedOutPss ?
+                        memInfo.nativeSwappedOutPss : memInfo.nativeSwappedOut,
+                        nativeMax, nativeAllocated, nativeFree);
+                printRow(pw, HEAP_FULL_COLUMN, "Dalvik Heap", memInfo.dalvikPss,
+                        memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty,
+                        memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean,
+                        memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss ?
+                        memInfo.dalvikSwappedOutPss : memInfo.dalvikSwappedOut,
+                        dalvikMax, dalvikAllocated, dalvikFree);
+            } else {
+                printRow(pw, HEAP_COLUMN, "", "Pss", "Private",
+                        "Private", memInfo.hasSwappedOutPss ? "SwapPss" : "Swap",
+                        "Heap", "Heap", "Heap");
+                printRow(pw, HEAP_COLUMN, "", "Total", "Dirty",
+                        "Clean", "Dirty", "Size", "Alloc", "Free");
+                printRow(pw, HEAP_COLUMN, "", "------", "------", "------",
+                        "------", "------", "------", "------", "------");
+                printRow(pw, HEAP_COLUMN, "Native Heap", memInfo.nativePss,
+                        memInfo.nativePrivateDirty,
+                        memInfo.nativePrivateClean,
+                        memInfo.hasSwappedOutPss ? memInfo.nativeSwappedOutPss :
+                        memInfo.nativeSwappedOut,
+                        nativeMax, nativeAllocated, nativeFree);
+                printRow(pw, HEAP_COLUMN, "Dalvik Heap", memInfo.dalvikPss,
+                        memInfo.dalvikPrivateDirty,
+                        memInfo.dalvikPrivateClean,
+                        memInfo.hasSwappedOutPss ? memInfo.dalvikSwappedOutPss :
+                        memInfo.dalvikSwappedOut,
+                        dalvikMax, dalvikAllocated, dalvikFree);
+            }
+
+            int otherPss = memInfo.otherPss;
+            int otherSwappablePss = memInfo.otherSwappablePss;
+            int otherSharedDirty = memInfo.otherSharedDirty;
+            int otherPrivateDirty = memInfo.otherPrivateDirty;
+            int otherSharedClean = memInfo.otherSharedClean;
+            int otherPrivateClean = memInfo.otherPrivateClean;
+            int otherSwappedOut = memInfo.otherSwappedOut;
+            int otherSwappedOutPss = memInfo.otherSwappedOutPss;
+
+            for (int i=0; i<Debug.MemoryInfo.NUM_OTHER_STATS; i++) {
+                final int myPss = memInfo.getOtherPss(i);
+                final int mySwappablePss = memInfo.getOtherSwappablePss(i);
+                final int mySharedDirty = memInfo.getOtherSharedDirty(i);
+                final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
+                final int mySharedClean = memInfo.getOtherSharedClean(i);
+                final int myPrivateClean = memInfo.getOtherPrivateClean(i);
+                final int mySwappedOut = memInfo.getOtherSwappedOut(i);
+                final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
+                if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
+                        || mySharedClean != 0 || myPrivateClean != 0
+                        || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
+                    if (dumpFullInfo) {
+                        printRow(pw, HEAP_FULL_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
+                                myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
+                                mySharedClean, myPrivateClean,
+                                memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
+                                "", "", "");
+                    } else {
+                        printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
+                                myPss, myPrivateDirty,
+                                myPrivateClean,
+                                memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
+                                "", "", "");
+                    }
+                    otherPss -= myPss;
+                    otherSwappablePss -= mySwappablePss;
+                    otherSharedDirty -= mySharedDirty;
+                    otherPrivateDirty -= myPrivateDirty;
+                    otherSharedClean -= mySharedClean;
+                    otherPrivateClean -= myPrivateClean;
+                    otherSwappedOut -= mySwappedOut;
+                    otherSwappedOutPss -= mySwappedOutPss;
+                }
+            }
+
+            if (dumpFullInfo) {
+                printRow(pw, HEAP_FULL_COLUMN, "Unknown", otherPss, otherSwappablePss,
+                        otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean,
+                        memInfo.hasSwappedOutPss ? otherSwappedOutPss : otherSwappedOut,
+                        "", "", "");
+                printRow(pw, HEAP_FULL_COLUMN, "TOTAL", memInfo.getTotalPss(),
+                        memInfo.getTotalSwappablePss(),
+                        memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(),
+                        memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(),
+                        memInfo.hasSwappedOutPss ? memInfo.getTotalSwappedOutPss() :
+                        memInfo.getTotalSwappedOut(),
+                        nativeMax+dalvikMax, nativeAllocated+dalvikAllocated,
+                        nativeFree+dalvikFree);
+            } else {
+                printRow(pw, HEAP_COLUMN, "Unknown", otherPss,
+                        otherPrivateDirty, otherPrivateClean,
+                        memInfo.hasSwappedOutPss ? otherSwappedOutPss : otherSwappedOut,
+                        "", "", "");
+                printRow(pw, HEAP_COLUMN, "TOTAL", memInfo.getTotalPss(),
+                        memInfo.getTotalPrivateDirty(),
+                        memInfo.getTotalPrivateClean(),
+                        memInfo.hasSwappedOutPss ? memInfo.getTotalSwappedOutPss() :
+                        memInfo.getTotalSwappedOut(),
+                        nativeMax+dalvikMax,
+                        nativeAllocated+dalvikAllocated, nativeFree+dalvikFree);
+            }
+
+            if (dumpDalvik) {
+                pw.println(" ");
+                pw.println(" Dalvik Details");
+
+                for (int i=Debug.MemoryInfo.NUM_OTHER_STATS;
+                     i<Debug.MemoryInfo.NUM_OTHER_STATS + Debug.MemoryInfo.NUM_DVK_STATS; i++) {
+                    final int myPss = memInfo.getOtherPss(i);
+                    final int mySwappablePss = memInfo.getOtherSwappablePss(i);
+                    final int mySharedDirty = memInfo.getOtherSharedDirty(i);
+                    final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
+                    final int mySharedClean = memInfo.getOtherSharedClean(i);
+                    final int myPrivateClean = memInfo.getOtherPrivateClean(i);
+                    final int mySwappedOut = memInfo.getOtherSwappedOut(i);
+                    final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
+                    if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
+                            || mySharedClean != 0 || myPrivateClean != 0
+                            || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
+                        if (dumpFullInfo) {
+                            printRow(pw, HEAP_FULL_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
+                                    myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
+                                    mySharedClean, myPrivateClean,
+                                    memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
+                                    "", "", "");
+                        } else {
+                            printRow(pw, HEAP_COLUMN, Debug.MemoryInfo.getOtherLabel(i),
+                                    myPss, myPrivateDirty,
+                                    myPrivateClean,
+                                    memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut,
+                                    "", "", "");
+                        }
+                    }
+                }
+            }
+        }
+
+        pw.println(" ");
+        pw.println(" App Summary");
+        printRow(pw, ONE_COUNT_COLUMN_HEADER, "", "Pss(KB)");
+        printRow(pw, ONE_COUNT_COLUMN_HEADER, "", "------");
+        printRow(pw, ONE_COUNT_COLUMN,
+            "Java Heap:", memInfo.getSummaryJavaHeap());
+        printRow(pw, ONE_COUNT_COLUMN,
+            "Native Heap:", memInfo.getSummaryNativeHeap());
+        printRow(pw, ONE_COUNT_COLUMN,
+            "Code:", memInfo.getSummaryCode());
+        printRow(pw, ONE_COUNT_COLUMN,
+            "Stack:", memInfo.getSummaryStack());
+        printRow(pw, ONE_COUNT_COLUMN,
+            "Graphics:", memInfo.getSummaryGraphics());
+        printRow(pw, ONE_COUNT_COLUMN,
+            "Private Other:", memInfo.getSummaryPrivateOther());
+        printRow(pw, ONE_COUNT_COLUMN,
+            "System:", memInfo.getSummarySystem());
+        pw.println(" ");
+        if (memInfo.hasSwappedOutPss) {
+            printRow(pw, TWO_COUNT_COLUMNS,
+                "TOTAL:", memInfo.getSummaryTotalPss(),
+                "TOTAL SWAP PSS:", memInfo.getSummaryTotalSwapPss());
+        } else {
+            printRow(pw, TWO_COUNT_COLUMNS,
+                "TOTAL:", memInfo.getSummaryTotalPss(),
+                "TOTAL SWAP (KB):", memInfo.getSummaryTotalSwap());
+        }
+    }
+
+    /**
+     * Dump heap info to proto.
+     *
+     * @param hasSwappedOutPss determines whether to use dirtySwap or dirtySwapPss
+     */
+    private static void dumpMemoryInfo(ProtoOutputStream proto, long fieldId, String name,
+            int pss, int cleanPss, int sharedDirty, int privateDirty,
+            int sharedClean, int privateClean,
+            boolean hasSwappedOutPss, int dirtySwap, int dirtySwapPss) {
+        final long token = proto.start(fieldId);
+
+        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.NAME, name);
+        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.TOTAL_PSS_KB, pss);
+        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.CLEAN_PSS_KB, cleanPss);
+        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.SHARED_DIRTY_KB, sharedDirty);
+        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.PRIVATE_DIRTY_KB, privateDirty);
+        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.SHARED_CLEAN_KB, sharedClean);
+        proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.PRIVATE_CLEAN_KB, privateClean);
+        if (hasSwappedOutPss) {
+            proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_PSS_KB, dirtySwapPss);
+        } else {
+            proto.write(MemInfoDumpProto.ProcessMemory.MemoryInfo.DIRTY_SWAP_KB, dirtySwap);
+        }
+
+        proto.end(token);
+    }
+
+    /**
+     * Dump mem info data to proto.
+     */
+    public static void dumpMemInfoTable(ProtoOutputStream proto, Debug.MemoryInfo memInfo,
+            boolean dumpDalvik, boolean dumpSummaryOnly,
+            long nativeMax, long nativeAllocated, long nativeFree,
+            long dalvikMax, long dalvikAllocated, long dalvikFree) {
+
+        if (!dumpSummaryOnly) {
+            final long nhToken = proto.start(MemInfoDumpProto.ProcessMemory.NATIVE_HEAP);
+            dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "Native Heap",
+                    memInfo.nativePss, memInfo.nativeSwappablePss, memInfo.nativeSharedDirty,
+                    memInfo.nativePrivateDirty, memInfo.nativeSharedClean,
+                    memInfo.nativePrivateClean, memInfo.hasSwappedOutPss,
+                    memInfo.nativeSwappedOut, memInfo.nativeSwappedOutPss);
+            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, nativeMax);
+            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, nativeAllocated);
+            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, nativeFree);
+            proto.end(nhToken);
+
+            final long dvToken = proto.start(MemInfoDumpProto.ProcessMemory.DALVIK_HEAP);
+            dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "Dalvik Heap",
+                    memInfo.dalvikPss, memInfo.dalvikSwappablePss, memInfo.dalvikSharedDirty,
+                    memInfo.dalvikPrivateDirty, memInfo.dalvikSharedClean,
+                    memInfo.dalvikPrivateClean, memInfo.hasSwappedOutPss,
+                    memInfo.dalvikSwappedOut, memInfo.dalvikSwappedOutPss);
+            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB, dalvikMax);
+            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB, dalvikAllocated);
+            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB, dalvikFree);
+            proto.end(dvToken);
+
+            int otherPss = memInfo.otherPss;
+            int otherSwappablePss = memInfo.otherSwappablePss;
+            int otherSharedDirty = memInfo.otherSharedDirty;
+            int otherPrivateDirty = memInfo.otherPrivateDirty;
+            int otherSharedClean = memInfo.otherSharedClean;
+            int otherPrivateClean = memInfo.otherPrivateClean;
+            int otherSwappedOut = memInfo.otherSwappedOut;
+            int otherSwappedOutPss = memInfo.otherSwappedOutPss;
+
+            for (int i = 0; i < Debug.MemoryInfo.NUM_OTHER_STATS; i++) {
+                final int myPss = memInfo.getOtherPss(i);
+                final int mySwappablePss = memInfo.getOtherSwappablePss(i);
+                final int mySharedDirty = memInfo.getOtherSharedDirty(i);
+                final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
+                final int mySharedClean = memInfo.getOtherSharedClean(i);
+                final int myPrivateClean = memInfo.getOtherPrivateClean(i);
+                final int mySwappedOut = memInfo.getOtherSwappedOut(i);
+                final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
+                if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
+                        || mySharedClean != 0 || myPrivateClean != 0
+                        || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
+                    dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.OTHER_HEAPS,
+                            Debug.MemoryInfo.getOtherLabel(i),
+                            myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
+                            mySharedClean, myPrivateClean,
+                            memInfo.hasSwappedOutPss, mySwappedOut, mySwappedOutPss);
+
+                    otherPss -= myPss;
+                    otherSwappablePss -= mySwappablePss;
+                    otherSharedDirty -= mySharedDirty;
+                    otherPrivateDirty -= myPrivateDirty;
+                    otherSharedClean -= mySharedClean;
+                    otherPrivateClean -= myPrivateClean;
+                    otherSwappedOut -= mySwappedOut;
+                    otherSwappedOutPss -= mySwappedOutPss;
+                }
+            }
+
+            dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.UNKNOWN_HEAP, "Unknown",
+                    otherPss, otherSwappablePss,
+                    otherSharedDirty, otherPrivateDirty, otherSharedClean, otherPrivateClean,
+                    memInfo.hasSwappedOutPss, otherSwappedOut, otherSwappedOutPss);
+            final long tToken = proto.start(MemInfoDumpProto.ProcessMemory.TOTAL_HEAP);
+            dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.HeapInfo.MEM_INFO, "TOTAL",
+                    memInfo.getTotalPss(), memInfo.getTotalSwappablePss(),
+                    memInfo.getTotalSharedDirty(), memInfo.getTotalPrivateDirty(),
+                    memInfo.getTotalSharedClean(), memInfo.getTotalPrivateClean(),
+                    memInfo.hasSwappedOutPss, memInfo.getTotalSwappedOut(),
+                    memInfo.getTotalSwappedOutPss());
+            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_SIZE_KB,
+                    nativeMax + dalvikMax);
+            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_ALLOC_KB,
+                    nativeAllocated + dalvikAllocated);
+            proto.write(MemInfoDumpProto.ProcessMemory.HeapInfo.HEAP_FREE_KB,
+                    nativeFree + dalvikFree);
+            proto.end(tToken);
+
+            if (dumpDalvik) {
+                for (int i = Debug.MemoryInfo.NUM_OTHER_STATS;
+                        i < Debug.MemoryInfo.NUM_OTHER_STATS + Debug.MemoryInfo.NUM_DVK_STATS;
+                        i++) {
+                    final int myPss = memInfo.getOtherPss(i);
+                    final int mySwappablePss = memInfo.getOtherSwappablePss(i);
+                    final int mySharedDirty = memInfo.getOtherSharedDirty(i);
+                    final int myPrivateDirty = memInfo.getOtherPrivateDirty(i);
+                    final int mySharedClean = memInfo.getOtherSharedClean(i);
+                    final int myPrivateClean = memInfo.getOtherPrivateClean(i);
+                    final int mySwappedOut = memInfo.getOtherSwappedOut(i);
+                    final int mySwappedOutPss = memInfo.getOtherSwappedOutPss(i);
+                    if (myPss != 0 || mySharedDirty != 0 || myPrivateDirty != 0
+                            || mySharedClean != 0 || myPrivateClean != 0
+                            || (memInfo.hasSwappedOutPss ? mySwappedOutPss : mySwappedOut) != 0) {
+                        dumpMemoryInfo(proto, MemInfoDumpProto.ProcessMemory.DALVIK_DETAILS,
+                                Debug.MemoryInfo.getOtherLabel(i),
+                                myPss, mySwappablePss, mySharedDirty, myPrivateDirty,
+                                mySharedClean, myPrivateClean,
+                                memInfo.hasSwappedOutPss, mySwappedOut, mySwappedOutPss);
+                    }
+                }
+            }
+        }
+
+        final long asToken = proto.start(MemInfoDumpProto.ProcessMemory.APP_SUMMARY);
+        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.JAVA_HEAP_PSS_KB,
+                memInfo.getSummaryJavaHeap());
+        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.NATIVE_HEAP_PSS_KB,
+                memInfo.getSummaryNativeHeap());
+        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.CODE_PSS_KB,
+                memInfo.getSummaryCode());
+        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.STACK_PSS_KB,
+                memInfo.getSummaryStack());
+        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.GRAPHICS_PSS_KB,
+                memInfo.getSummaryGraphics());
+        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.PRIVATE_OTHER_PSS_KB,
+                memInfo.getSummaryPrivateOther());
+        proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.SYSTEM_PSS_KB,
+                memInfo.getSummarySystem());
+        if (memInfo.hasSwappedOutPss) {
+            proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
+                    memInfo.getSummaryTotalSwapPss());
+        } else {
+            proto.write(MemInfoDumpProto.ProcessMemory.AppSummary.TOTAL_SWAP_PSS,
+                    memInfo.getSummaryTotalSwap());
+        }
+        proto.end(asToken);
+    }
+
+    @UnsupportedAppUsage
+    public void registerOnActivityPausedListener(Activity activity,
+            OnActivityPausedListener listener) {
+        synchronized (mOnPauseListeners) {
+            ArrayList<OnActivityPausedListener> list = mOnPauseListeners.get(activity);
+            if (list == null) {
+                list = new ArrayList<OnActivityPausedListener>();
+                mOnPauseListeners.put(activity, list);
+            }
+            list.add(listener);
+        }
+    }
+
+    @UnsupportedAppUsage
+    public void unregisterOnActivityPausedListener(Activity activity,
+            OnActivityPausedListener listener) {
+        synchronized (mOnPauseListeners) {
+            ArrayList<OnActivityPausedListener> list = mOnPauseListeners.get(activity);
+            if (list != null) {
+                list.remove(listener);
+            }
+        }
+    }
+
+    public final ActivityInfo resolveActivityInfo(Intent intent) {
+        ActivityInfo aInfo = intent.resolveActivityInfo(
+                mInitialApplication.getPackageManager(), PackageManager.GET_SHARED_LIBRARY_FILES);
+        if (aInfo == null) {
+            // Throw an exception.
+            Instrumentation.checkStartActivityResult(
+                    ActivityManager.START_CLASS_NOT_FOUND, intent);
+        }
+        return aInfo;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    public final Activity startActivityNow(Activity parent, String id,
+        Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state,
+        Activity.NonConfigurationInstances lastNonConfigurationInstances, IBinder assistToken) {
+        ActivityClientRecord r = new ActivityClientRecord();
+            r.token = token;
+            r.assistToken = assistToken;
+            r.ident = 0;
+            r.intent = intent;
+            r.state = state;
+            r.parent = parent;
+            r.embeddedID = id;
+            r.activityInfo = activityInfo;
+            r.lastNonConfigurationInstances = lastNonConfigurationInstances;
+        if (localLOGV) {
+            ComponentName compname = intent.getComponent();
+            String name;
+            if (compname != null) {
+                name = compname.toShortString();
+            } else {
+                name = "(Intent " + intent + ").getComponent() returned null";
+            }
+            Slog.v(TAG, "Performing launch: action=" + intent.getAction()
+                    + ", comp=" + name
+                    + ", token=" + token);
+        }
+        // TODO(lifecycler): Can't switch to use #handleLaunchActivity() because it will try to
+        // call #reportSizeConfigurations(), but the server might not know anything about the
+        // activity if it was launched from LocalAcvitivyManager.
+        return performLaunchActivity(r, null /* customIntent */);
+    }
+
+    @UnsupportedAppUsage
+    public final Activity getActivity(IBinder token) {
+        final ActivityClientRecord activityRecord = mActivities.get(token);
+        return activityRecord != null ? activityRecord.activity : null;
+    }
+
+    @Override
+    public ActivityClientRecord getActivityClient(IBinder token) {
+        return mActivities.get(token);
+    }
+
+    @Override
+    public void updatePendingConfiguration(Configuration config) {
+        synchronized (mResourcesManager) {
+            if (mPendingConfiguration == null || mPendingConfiguration.isOtherSeqNewer(config)) {
+                mPendingConfiguration = config;
+            }
+        }
+    }
+
+    @Override
+    public void updateProcessState(int processState, boolean fromIpc) {
+        synchronized (mAppThread) {
+            if (mLastProcessState == processState) {
+                return;
+            }
+            mLastProcessState = processState;
+            // Defer the top state for VM to avoid aggressive JIT compilation affecting activity
+            // launch time.
+            if (processState == ActivityManager.PROCESS_STATE_TOP
+                    && mNumLaunchingActivities.get() > 0) {
+                mPendingProcessState = processState;
+                mH.postDelayed(this::applyPendingProcessState, PENDING_TOP_PROCESS_STATE_TIMEOUT);
+            } else {
+                mPendingProcessState = PROCESS_STATE_UNKNOWN;
+                updateVmProcessState(processState);
+            }
+            if (localLOGV) {
+                Slog.i(TAG, "******************* PROCESS STATE CHANGED TO: " + processState
+                        + (fromIpc ? " (from ipc" : ""));
+            }
+        }
+    }
+
+    /** Update VM state based on ActivityManager.PROCESS_STATE_* constants. */
+    private void updateVmProcessState(int processState) {
+        // TODO: Tune this since things like gmail sync are important background but not jank
+        // perceptible.
+        final int state = processState <= ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND
+                ? VM_PROCESS_STATE_JANK_PERCEPTIBLE
+                : VM_PROCESS_STATE_JANK_IMPERCEPTIBLE;
+        VMRuntime.getRuntime().updateProcessState(state);
+    }
+
+    private void applyPendingProcessState() {
+        synchronized (mAppThread) {
+            if (mPendingProcessState == PROCESS_STATE_UNKNOWN) {
+                return;
+            }
+            final int pendingState = mPendingProcessState;
+            mPendingProcessState = PROCESS_STATE_UNKNOWN;
+            // Only apply the pending state if the last state doesn't change.
+            if (pendingState == mLastProcessState) {
+                updateVmProcessState(pendingState);
+            }
+        }
+    }
+
+    @Override
+    public void countLaunchingActivities(int num) {
+        mNumLaunchingActivities.getAndAdd(num);
+    }
+
+    @UnsupportedAppUsage
+    public final void sendActivityResult(
+            IBinder token, String id, int requestCode,
+            int resultCode, Intent data) {
+        if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id
+                + " req=" + requestCode + " res=" + resultCode + " data=" + data);
+        ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
+        list.add(new ResultInfo(id, requestCode, resultCode, data));
+        final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread, token);
+        clientTransaction.addCallback(ActivityResultItem.obtain(list));
+        try {
+            mAppThread.scheduleTransaction(clientTransaction);
+        } catch (RemoteException e) {
+            // Local scheduling
+        }
+    }
+
+    @Override
+    TransactionExecutor getTransactionExecutor() {
+        return mTransactionExecutor;
+    }
+
+    void sendMessage(int what, Object obj) {
+        sendMessage(what, obj, 0, 0, false);
+    }
+
+    private void sendMessage(int what, Object obj, int arg1) {
+        sendMessage(what, obj, arg1, 0, false);
+    }
+
+    private void sendMessage(int what, Object obj, int arg1, int arg2) {
+        sendMessage(what, obj, arg1, arg2, false);
+    }
+
+    private void sendMessage(int what, Object obj, int arg1, int arg2, boolean async) {
+        if (DEBUG_MESSAGES) {
+            Slog.v(TAG,
+                    "SCHEDULE " + what + " " + mH.codeToString(what) + ": " + arg1 + " / " + obj);
+        }
+        Message msg = Message.obtain();
+        msg.what = what;
+        msg.obj = obj;
+        msg.arg1 = arg1;
+        msg.arg2 = arg2;
+        if (async) {
+            msg.setAsynchronous(true);
+        }
+        mH.sendMessage(msg);
+    }
+
+    private void sendMessage(int what, Object obj, int arg1, int arg2, int seq) {
+        if (DEBUG_MESSAGES) Slog.v(
+                TAG, "SCHEDULE " + mH.codeToString(what) + " arg1=" + arg1 + " arg2=" + arg2 +
+                        "seq= " + seq);
+        Message msg = Message.obtain();
+        msg.what = what;
+        SomeArgs args = SomeArgs.obtain();
+        args.arg1 = obj;
+        args.argi1 = arg1;
+        args.argi2 = arg2;
+        args.argi3 = seq;
+        msg.obj = args;
+        mH.sendMessage(msg);
+    }
+
+    final void scheduleContextCleanup(ContextImpl context, String who,
+            String what) {
+        ContextCleanupInfo cci = new ContextCleanupInfo();
+        cci.context = context;
+        cci.who = who;
+        cci.what = what;
+        sendMessage(H.CLEAN_UP_CONTEXT, cci);
+    }
+
+    /**  Core implementation of activity launch. */
+    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
+        ActivityInfo aInfo = r.activityInfo;
+        if (r.packageInfo == null) {
+            r.packageInfo = getPackageInfo(aInfo.applicationInfo, r.compatInfo,
+                    Context.CONTEXT_INCLUDE_CODE);
+        }
+
+        ComponentName component = r.intent.getComponent();
+        if (component == null) {
+            component = r.intent.resolveActivity(
+                mInitialApplication.getPackageManager());
+            r.intent.setComponent(component);
+        }
+
+        if (r.activityInfo.targetActivity != null) {
+            component = new ComponentName(r.activityInfo.packageName,
+                    r.activityInfo.targetActivity);
+        }
+
+        ContextImpl appContext = createBaseContextForActivity(r);
+        Activity activity = null;
+        try {
+            java.lang.ClassLoader cl = appContext.getClassLoader();
+            activity = mInstrumentation.newActivity(
+                    cl, component.getClassName(), r.intent);
+            StrictMode.incrementExpectedActivityCount(activity.getClass());
+            r.intent.setExtrasClassLoader(cl);
+            r.intent.prepareToEnterProcess();
+            if (r.state != null) {
+                r.state.setClassLoader(cl);
+            }
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(activity, e)) {
+                throw new RuntimeException(
+                    "Unable to instantiate activity " + component
+                    + ": " + e.toString(), e);
+            }
+        }
+
+        try {
+            Application app = r.packageInfo.makeApplication(false, mInstrumentation);
+
+            if (localLOGV) Slog.v(TAG, "Performing launch of " + r);
+            if (localLOGV) Slog.v(
+                    TAG, r + ": app=" + app
+                    + ", appName=" + app.getPackageName()
+                    + ", pkg=" + r.packageInfo.getPackageName()
+                    + ", comp=" + r.intent.getComponent().toShortString()
+                    + ", dir=" + r.packageInfo.getAppDir());
+
+            if (activity != null) {
+                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
+                Configuration config = new Configuration(mCompatConfiguration);
+                if (r.overrideConfig != null) {
+                    config.updateFrom(r.overrideConfig);
+                }
+                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+                        + r.activityInfo.name + " with config " + config);
+                Window window = null;
+                if (r.mPendingRemoveWindow != null && r.mPreserveWindow) {
+                    window = r.mPendingRemoveWindow;
+                    r.mPendingRemoveWindow = null;
+                    r.mPendingRemoveWindowManager = null;
+                }
+                appContext.setOuterContext(activity);
+                activity.attach(appContext, this, getInstrumentation(), r.token,
+                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
+                        r.embeddedID, r.lastNonConfigurationInstances, config,
+                        r.referrer, r.voiceInteractor, window, r.configCallback,
+                        r.assistToken);
+
+                if (customIntent != null) {
+                    activity.mIntent = customIntent;
+                }
+                r.lastNonConfigurationInstances = null;
+                checkAndBlockForNetworkAccess();
+                activity.mStartedActivity = false;
+                int theme = r.activityInfo.getThemeResource();
+                if (theme != 0) {
+                    activity.setTheme(theme);
+                }
+
+                activity.mCalled = false;
+                if (r.isPersistable()) {
+                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
+                } else {
+                    mInstrumentation.callActivityOnCreate(activity, r.state);
+                }
+                if (!activity.mCalled) {
+                    throw new SuperNotCalledException(
+                        "Activity " + r.intent.getComponent().toShortString() +
+                        " did not call through to super.onCreate()");
+                }
+                r.activity = activity;
+            }
+            r.setState(ON_CREATE);
+
+            // updatePendingActivityConfiguration() reads from mActivities to update
+            // ActivityClientRecord which runs in a different thread. Protect modifications to
+            // mActivities to avoid race.
+            synchronized (mResourcesManager) {
+                mActivities.put(r.token, r);
+            }
+
+        } catch (SuperNotCalledException e) {
+            throw e;
+
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(activity, e)) {
+                throw new RuntimeException(
+                    "Unable to start activity " + component
+                    + ": " + e.toString(), e);
+            }
+        }
+
+        return activity;
+    }
+
+    @Override
+    public void handleStartActivity(ActivityClientRecord r,
+            PendingTransactionActions pendingActions) {
+        final Activity activity = r.activity;
+        if (r.activity == null) {
+            // TODO(lifecycler): What do we do in this case?
+            return;
+        }
+        if (!r.stopped) {
+            throw new IllegalStateException("Can't start activity that is not stopped.");
+        }
+        if (r.activity.mFinished) {
+            // TODO(lifecycler): How can this happen?
+            return;
+        }
+
+        // Start
+        activity.performStart("handleStartActivity");
+        r.setState(ON_START);
+
+        if (pendingActions == null) {
+            // No more work to do.
+            return;
+        }
+
+        // Restore instance state
+        if (pendingActions.shouldRestoreInstanceState()) {
+            if (r.isPersistable()) {
+                if (r.state != null || r.persistentState != null) {
+                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
+                            r.persistentState);
+                }
+            } else if (r.state != null) {
+                mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
+            }
+        }
+
+        // Call postOnCreate()
+        if (pendingActions.shouldCallOnPostCreate()) {
+            activity.mCalled = false;
+            if (r.isPersistable()) {
+                mInstrumentation.callActivityOnPostCreate(activity, r.state,
+                        r.persistentState);
+            } else {
+                mInstrumentation.callActivityOnPostCreate(activity, r.state);
+            }
+            if (!activity.mCalled) {
+                throw new SuperNotCalledException(
+                        "Activity " + r.intent.getComponent().toShortString()
+                                + " did not call through to super.onPostCreate()");
+            }
+        }
+    }
+
+    /**
+     * Checks if {@link #mNetworkBlockSeq} is {@link #INVALID_PROC_STATE_SEQ} and if so, returns
+     * immediately. Otherwise, makes a blocking call to ActivityManagerService to wait for the
+     * network rules to get updated.
+     */
+    private void checkAndBlockForNetworkAccess() {
+        synchronized (mNetworkPolicyLock) {
+            if (mNetworkBlockSeq != INVALID_PROC_STATE_SEQ) {
+                try {
+                    ActivityManager.getService().waitForNetworkStateUpdate(mNetworkBlockSeq);
+                    mNetworkBlockSeq = INVALID_PROC_STATE_SEQ;
+                } catch (RemoteException ignored) {}
+            }
+        }
+    }
+
+    private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
+        final int displayId;
+        try {
+            displayId = ActivityTaskManager.getService().getActivityDisplayId(r.token);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        ContextImpl appContext = ContextImpl.createActivityContext(
+                this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
+
+        final DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
+        // For debugging purposes, if the activity's package name contains the value of
+        // the "debug.use-second-display" system property as a substring, then show
+        // its content on a secondary display if there is one.
+        String pkgName = SystemProperties.get("debug.second-display.pkg");
+        if (pkgName != null && !pkgName.isEmpty()
+                && r.packageInfo.mPackageName.contains(pkgName)) {
+            for (int id : dm.getDisplayIds()) {
+                if (id != Display.DEFAULT_DISPLAY) {
+                    Display display =
+                            dm.getCompatibleDisplay(id, appContext.getResources());
+                    appContext = (ContextImpl) appContext.createDisplayContext(display);
+                    break;
+                }
+            }
+        }
+        return appContext;
+    }
+
+    /**
+     * Extended implementation of activity launch. Used when server requests a launch or relaunch.
+     */
+    @Override
+    public Activity handleLaunchActivity(ActivityClientRecord r,
+            PendingTransactionActions pendingActions, Intent customIntent) {
+        // If we are getting ready to gc after going to the background, well
+        // we are back active so skip it.
+        unscheduleGcIdler();
+        mSomeActivitiesChanged = true;
+
+        if (r.profilerInfo != null) {
+            mProfiler.setProfiler(r.profilerInfo);
+            mProfiler.startProfiling();
+        }
+
+        // Make sure we are running with the most recent config.
+        handleConfigurationChanged(null, null);
+
+        if (localLOGV) Slog.v(
+            TAG, "Handling launch of " + r);
+
+        // Initialize before creating the activity
+        if (!ThreadedRenderer.sRendererDisabled
+                && (r.activityInfo.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
+            HardwareRenderer.preload();
+        }
+        WindowManagerGlobal.initialize();
+
+        // Hint the GraphicsEnvironment that an activity is launching on the process.
+        GraphicsEnvironment.hintActivityLaunch();
+
+        final Activity a = performLaunchActivity(r, customIntent);
+
+        if (a != null) {
+            r.createdConfig = new Configuration(mConfiguration);
+            reportSizeConfigurations(r);
+            if (!r.activity.mFinished && pendingActions != null) {
+                pendingActions.setOldState(r.state);
+                pendingActions.setRestoreInstanceState(true);
+                pendingActions.setCallOnPostCreate(true);
+            }
+        } else {
+            // If there was an error, for any reason, tell the activity manager to stop us.
+            try {
+                ActivityTaskManager.getService()
+                        .finishActivity(r.token, Activity.RESULT_CANCELED, null,
+                                Activity.DONT_FINISH_TASK_WITH_ACTIVITY);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+
+        return a;
+    }
+
+    private void reportSizeConfigurations(ActivityClientRecord r) {
+        if (mActivitiesToBeDestroyed.containsKey(r.token)) {
+            // Size configurations of a destroyed activity is meaningless.
+            return;
+        }
+        Configuration[] configurations = r.activity.getResources().getSizeConfigurations();
+        if (configurations == null) {
+            return;
+        }
+        SparseIntArray horizontal = new SparseIntArray();
+        SparseIntArray vertical = new SparseIntArray();
+        SparseIntArray smallest = new SparseIntArray();
+        for (int i = configurations.length - 1; i >= 0; i--) {
+            Configuration config = configurations[i];
+            if (config.screenHeightDp != Configuration.SCREEN_HEIGHT_DP_UNDEFINED) {
+                vertical.put(config.screenHeightDp, 0);
+            }
+            if (config.screenWidthDp != Configuration.SCREEN_WIDTH_DP_UNDEFINED) {
+                horizontal.put(config.screenWidthDp, 0);
+            }
+            if (config.smallestScreenWidthDp != Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED) {
+                smallest.put(config.smallestScreenWidthDp, 0);
+            }
+        }
+        try {
+            ActivityTaskManager.getService().reportSizeConfigurations(r.token,
+                    horizontal.copyKeys(), vertical.copyKeys(), smallest.copyKeys());
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    private void deliverNewIntents(ActivityClientRecord r, List<ReferrerIntent> intents) {
+        final int N = intents.size();
+        for (int i=0; i<N; i++) {
+            ReferrerIntent intent = intents.get(i);
+            intent.setExtrasClassLoader(r.activity.getClassLoader());
+            intent.prepareToEnterProcess();
+            r.activity.mFragments.noteStateNotSaved();
+            mInstrumentation.callActivityOnNewIntent(r.activity, intent);
+        }
+    }
+
+    @Override
+    public void handleNewIntent(IBinder token, List<ReferrerIntent> intents) {
+        final ActivityClientRecord r = mActivities.get(token);
+        if (r == null) {
+            return;
+        }
+
+        checkAndBlockForNetworkAccess();
+        deliverNewIntents(r, intents);
+    }
+
+    public void handleRequestAssistContextExtras(RequestAssistContextExtras cmd) {
+        // Filling for autofill has a few differences:
+        // - it does not need an AssistContent
+        // - it does not call onProvideAssistData()
+        // - it needs an IAutoFillCallback
+        boolean forAutofill = cmd.requestType == ActivityManager.ASSIST_CONTEXT_AUTOFILL;
+
+        // TODO: decide if lastSessionId logic applies to autofill sessions
+        if (mLastSessionId != cmd.sessionId) {
+            // Clear the existing structures
+            mLastSessionId = cmd.sessionId;
+            for (int i = mLastAssistStructures.size() - 1; i >= 0; i--) {
+                AssistStructure structure = mLastAssistStructures.get(i).get();
+                if (structure != null) {
+                    structure.clearSendChannel();
+                }
+                mLastAssistStructures.remove(i);
+            }
+        }
+
+        Bundle data = new Bundle();
+        AssistStructure structure = null;
+        AssistContent content = forAutofill ? null : new AssistContent();
+        final long startTime = SystemClock.uptimeMillis();
+        ActivityClientRecord r = mActivities.get(cmd.activityToken);
+        Uri referrer = null;
+        if (r != null) {
+            if (!forAutofill) {
+                r.activity.getApplication().dispatchOnProvideAssistData(r.activity, data);
+                r.activity.onProvideAssistData(data);
+                referrer = r.activity.onProvideReferrer();
+            }
+            if (cmd.requestType == ActivityManager.ASSIST_CONTEXT_FULL || forAutofill) {
+                structure = new AssistStructure(r.activity, forAutofill, cmd.flags);
+                Intent activityIntent = r.activity.getIntent();
+                boolean notSecure = r.window == null ||
+                        (r.window.getAttributes().flags
+                                & WindowManager.LayoutParams.FLAG_SECURE) == 0;
+                if (activityIntent != null && notSecure) {
+                    if (!forAutofill) {
+                        Intent intent = new Intent(activityIntent);
+                        intent.setFlags(intent.getFlags() & ~(Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+                                | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION));
+                        intent.removeUnsafeExtras();
+                        content.setDefaultIntent(intent);
+                    }
+                } else {
+                    if (!forAutofill) {
+                        content.setDefaultIntent(new Intent());
+                    }
+                }
+                if (!forAutofill) {
+                    r.activity.onProvideAssistContent(content);
+                }
+            }
+
+        }
+        if (structure == null) {
+            structure = new AssistStructure();
+        }
+
+        // TODO: decide if lastSessionId logic applies to autofill sessions
+
+        structure.setAcquisitionStartTime(startTime);
+        structure.setAcquisitionEndTime(SystemClock.uptimeMillis());
+
+        mLastAssistStructures.add(new WeakReference<>(structure));
+        IActivityTaskManager mgr = ActivityTaskManager.getService();
+        try {
+            mgr.reportAssistContextExtras(cmd.requestToken, data, structure, content, referrer);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** Fetches the user actions for the corresponding activity */
+    private void handleRequestDirectActions(@NonNull IBinder activityToken,
+            @NonNull IVoiceInteractor interactor, @NonNull CancellationSignal cancellationSignal,
+            @NonNull RemoteCallback callback) {
+        final ActivityClientRecord r = mActivities.get(activityToken);
+        if (r == null) {
+            Log.w(TAG, "requestDirectActions(): no activity for " + activityToken);
+            callback.sendResult(null);
+            return;
+        }
+        final int lifecycleState = r.getLifecycleState();
+        if (lifecycleState < ON_START || lifecycleState >= ON_STOP) {
+            Log.w(TAG, "requestDirectActions(" + r + "): wrong lifecycle: " + lifecycleState);
+            callback.sendResult(null);
+            return;
+        }
+        if (r.activity.mVoiceInteractor == null
+                || r.activity.mVoiceInteractor.mInteractor.asBinder()
+                != interactor.asBinder()) {
+            if (r.activity.mVoiceInteractor != null) {
+                r.activity.mVoiceInteractor.destroy();
+            }
+            r.activity.mVoiceInteractor = new VoiceInteractor(interactor, r.activity,
+                    r.activity, Looper.myLooper());
+        }
+        r.activity.onGetDirectActions(cancellationSignal, (actions) -> {
+            Preconditions.checkNotNull(actions);
+            Preconditions.checkCollectionElementsNotNull(actions, "actions");
+            if (!actions.isEmpty()) {
+                final int actionCount = actions.size();
+                for (int i = 0; i < actionCount; i++) {
+                    final DirectAction action = actions.get(i);
+                    action.setSource(r.activity.getTaskId(), r.activity.getAssistToken());
+                }
+                final Bundle result = new Bundle();
+                result.putParcelable(DirectAction.KEY_ACTIONS_LIST,
+                        new ParceledListSlice<>(actions));
+                callback.sendResult(result);
+            } else {
+                callback.sendResult(null);
+            }
+        });
+    }
+
+    /** Performs an actions in the corresponding activity */
+    private void handlePerformDirectAction(@NonNull IBinder activityToken,
+            @NonNull String actionId, @Nullable Bundle arguments,
+            @NonNull CancellationSignal cancellationSignal,
+            @NonNull RemoteCallback resultCallback) {
+        final ActivityClientRecord r = mActivities.get(activityToken);
+        if (r != null) {
+            final int lifecycleState = r.getLifecycleState();
+            if (lifecycleState < ON_START || lifecycleState >= ON_STOP) {
+                resultCallback.sendResult(null);
+                return;
+            }
+            final Bundle nonNullArguments = (arguments != null) ? arguments : Bundle.EMPTY;
+            r.activity.onPerformDirectAction(actionId, nonNullArguments, cancellationSignal,
+                    resultCallback::sendResult);
+        } else {
+            resultCallback.sendResult(null);
+        }
+    }
+
+    public void handleTranslucentConversionComplete(IBinder token, boolean drawComplete) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (r != null) {
+            r.activity.onTranslucentConversionComplete(drawComplete);
+        }
+    }
+
+    public void onNewActivityOptions(IBinder token, ActivityOptions options) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (r != null) {
+            r.activity.onNewActivityOptions(options);
+        }
+    }
+
+    public void handleInstallProvider(ProviderInfo info) {
+        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+        try {
+            installContentProviders(mInitialApplication, Arrays.asList(info));
+        } finally {
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
+    private void handleEnterAnimationComplete(IBinder token) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (r != null) {
+            r.activity.dispatchEnterAnimationComplete();
+        }
+    }
+
+    private void handleStartBinderTracking() {
+        Binder.enableTracing();
+    }
+
+    private void handleStopBinderTrackingAndDump(ParcelFileDescriptor fd) {
+        try {
+            Binder.disableTracing();
+            Binder.getTransactionTracker().writeTracesToFile(fd);
+        } finally {
+            IoUtils.closeQuietly(fd);
+            Binder.getTransactionTracker().clearTraces();
+        }
+    }
+
+    @Override
+    public void handleMultiWindowModeChanged(IBinder token, boolean isInMultiWindowMode,
+            Configuration overrideConfig) {
+        final ActivityClientRecord r = mActivities.get(token);
+        if (r != null) {
+            final Configuration newConfig = new Configuration(mConfiguration);
+            if (overrideConfig != null) {
+                newConfig.updateFrom(overrideConfig);
+            }
+            r.activity.dispatchMultiWindowModeChanged(isInMultiWindowMode, newConfig);
+        }
+    }
+
+    @Override
+    public void handlePictureInPictureModeChanged(IBinder token, boolean isInPipMode,
+            Configuration overrideConfig) {
+        final ActivityClientRecord r = mActivities.get(token);
+        if (r != null) {
+            final Configuration newConfig = new Configuration(mConfiguration);
+            if (overrideConfig != null) {
+                newConfig.updateFrom(overrideConfig);
+            }
+            r.activity.dispatchPictureInPictureModeChanged(isInPipMode, newConfig);
+        }
+    }
+
+    private void handleLocalVoiceInteractionStarted(IBinder token, IVoiceInteractor interactor) {
+        final ActivityClientRecord r = mActivities.get(token);
+        if (r != null) {
+            r.voiceInteractor = interactor;
+            r.activity.setVoiceInteractor(interactor);
+            if (interactor == null) {
+                r.activity.onLocalVoiceInteractionStopped();
+            } else {
+                r.activity.onLocalVoiceInteractionStarted();
+            }
+        }
+    }
+
+    private static boolean attemptAttachAgent(String agent, ClassLoader classLoader) {
+        try {
+            VMDebug.attachAgent(agent, classLoader);
+            return true;
+        } catch (IOException e) {
+            Slog.e(TAG, "Attaching agent with " + classLoader + " failed: " + agent);
+            return false;
+        }
+    }
+
+    static void handleAttachAgent(String agent, LoadedApk loadedApk) {
+        ClassLoader classLoader = loadedApk != null ? loadedApk.getClassLoader() : null;
+        if (attemptAttachAgent(agent, classLoader)) {
+            return;
+        }
+        if (classLoader != null) {
+            attemptAttachAgent(agent, null);
+        }
+    }
+
+    private static final ThreadLocal<Intent> sCurrentBroadcastIntent = new ThreadLocal<Intent>();
+
+    /**
+     * Return the Intent that's currently being handled by a
+     * BroadcastReceiver on this thread, or null if none.
+     * @hide
+     */
+    public static Intent getIntentBeingBroadcast() {
+        return sCurrentBroadcastIntent.get();
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private void handleReceiver(ReceiverData data) {
+        // If we are getting ready to gc after going to the background, well
+        // we are back active so skip it.
+        unscheduleGcIdler();
+
+        String component = data.intent.getComponent().getClassName();
+
+        LoadedApk packageInfo = getPackageInfoNoCheck(
+                data.info.applicationInfo, data.compatInfo);
+
+        IActivityManager mgr = ActivityManager.getService();
+
+        Application app;
+        BroadcastReceiver receiver;
+        ContextImpl context;
+        try {
+            app = packageInfo.makeApplication(false, mInstrumentation);
+            context = (ContextImpl) app.getBaseContext();
+            if (data.info.splitName != null) {
+                context = (ContextImpl) context.createContextForSplit(data.info.splitName);
+            }
+            java.lang.ClassLoader cl = context.getClassLoader();
+            data.intent.setExtrasClassLoader(cl);
+            data.intent.prepareToEnterProcess();
+            data.setExtrasClassLoader(cl);
+            receiver = packageInfo.getAppFactory()
+                    .instantiateReceiver(cl, data.info.name, data.intent);
+        } catch (Exception e) {
+            if (DEBUG_BROADCAST) Slog.i(TAG,
+                    "Finishing failed broadcast to " + data.intent.getComponent());
+            data.sendFinished(mgr);
+            throw new RuntimeException(
+                "Unable to instantiate receiver " + component
+                + ": " + e.toString(), e);
+        }
+
+        try {
+            if (localLOGV) Slog.v(
+                TAG, "Performing receive of " + data.intent
+                + ": app=" + app
+                + ", appName=" + app.getPackageName()
+                + ", pkg=" + packageInfo.getPackageName()
+                + ", comp=" + data.intent.getComponent().toShortString()
+                + ", dir=" + packageInfo.getAppDir());
+
+            sCurrentBroadcastIntent.set(data.intent);
+            receiver.setPendingResult(data);
+            receiver.onReceive(context.getReceiverRestrictedContext(),
+                    data.intent);
+        } catch (Exception e) {
+            if (DEBUG_BROADCAST) Slog.i(TAG,
+                    "Finishing failed broadcast to " + data.intent.getComponent());
+            data.sendFinished(mgr);
+            if (!mInstrumentation.onException(receiver, e)) {
+                throw new RuntimeException(
+                    "Unable to start receiver " + component
+                    + ": " + e.toString(), e);
+            }
+        } finally {
+            sCurrentBroadcastIntent.set(null);
+        }
+
+        if (receiver.getPendingResult() != null) {
+            data.finish();
+        }
+    }
+
+    // Instantiate a BackupAgent and tell it that it's alive
+    private void handleCreateBackupAgent(CreateBackupAgentData data) {
+        if (DEBUG_BACKUP) Slog.v(TAG, "handleCreateBackupAgent: " + data);
+
+        // Sanity check the requested target package's uid against ours
+        try {
+            PackageInfo requestedPackage = getPackageManager().getPackageInfo(
+                    data.appInfo.packageName, 0, UserHandle.myUserId());
+            if (requestedPackage.applicationInfo.uid != Process.myUid()) {
+                Slog.w(TAG, "Asked to instantiate non-matching package "
+                        + data.appInfo.packageName);
+                return;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        // no longer idle; we have backup work to do
+        unscheduleGcIdler();
+
+        // instantiate the BackupAgent class named in the manifest
+        LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+        String packageName = packageInfo.mPackageName;
+        if (packageName == null) {
+            Slog.d(TAG, "Asked to create backup agent for nonexistent package");
+            return;
+        }
+
+        String classname = data.appInfo.backupAgentName;
+        // full backup operation but no app-supplied agent?  use the default implementation
+        if (classname == null && (data.backupMode == ApplicationThreadConstants.BACKUP_MODE_FULL
+                || data.backupMode == ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL)) {
+            classname = "android.app.backup.FullBackupAgent";
+        }
+
+        try {
+            IBinder binder = null;
+            ArrayMap<String, BackupAgent> backupAgents = getBackupAgentsForUser(data.userId);
+            BackupAgent agent = backupAgents.get(packageName);
+            if (agent != null) {
+                // reusing the existing instance
+                if (DEBUG_BACKUP) {
+                    Slog.v(TAG, "Reusing existing agent instance");
+                }
+                binder = agent.onBind();
+            } else {
+                try {
+                    if (DEBUG_BACKUP) Slog.v(TAG, "Initializing agent class " + classname);
+
+                    java.lang.ClassLoader cl = packageInfo.getClassLoader();
+                    agent = (BackupAgent) cl.loadClass(classname).newInstance();
+
+                    // set up the agent's context
+                    ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
+                    context.setOuterContext(agent);
+                    agent.attach(context);
+
+                    agent.onCreate(UserHandle.of(data.userId));
+                    binder = agent.onBind();
+                    backupAgents.put(packageName, agent);
+                } catch (Exception e) {
+                    // If this is during restore, fail silently; otherwise go
+                    // ahead and let the user see the crash.
+                    Slog.e(TAG, "Agent threw during creation: " + e);
+                    if (data.backupMode != ApplicationThreadConstants.BACKUP_MODE_RESTORE
+                            && data.backupMode !=
+                                    ApplicationThreadConstants.BACKUP_MODE_RESTORE_FULL) {
+                        throw e;
+                    }
+                    // falling through with 'binder' still null
+                }
+            }
+
+            // tell the OS that we're live now
+            try {
+                ActivityManager.getService().backupAgentCreated(packageName, binder, data.userId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } catch (Exception e) {
+            throw new RuntimeException("Unable to create BackupAgent "
+                    + classname + ": " + e.toString(), e);
+        }
+    }
+
+    // Tear down a BackupAgent
+    private void handleDestroyBackupAgent(CreateBackupAgentData data) {
+        if (DEBUG_BACKUP) Slog.v(TAG, "handleDestroyBackupAgent: " + data);
+
+        LoadedApk packageInfo = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+        String packageName = packageInfo.mPackageName;
+        ArrayMap<String, BackupAgent> backupAgents = getBackupAgentsForUser(data.userId);
+        BackupAgent agent = backupAgents.get(packageName);
+        if (agent != null) {
+            try {
+                agent.onDestroy();
+            } catch (Exception e) {
+                Slog.w(TAG, "Exception thrown in onDestroy by backup agent of " + data.appInfo);
+                e.printStackTrace();
+            }
+            backupAgents.remove(packageName);
+        } else {
+            Slog.w(TAG, "Attempt to destroy unknown backup agent " + data);
+        }
+    }
+
+    private ArrayMap<String, BackupAgent> getBackupAgentsForUser(int userId) {
+        ArrayMap<String, BackupAgent> backupAgents = mBackupAgentsByUser.get(userId);
+        if (backupAgents == null) {
+            backupAgents = new ArrayMap<>();
+            mBackupAgentsByUser.put(userId, backupAgents);
+        }
+        return backupAgents;
+    }
+
+    @UnsupportedAppUsage
+    private void handleCreateService(CreateServiceData data) {
+        // If we are getting ready to gc after going to the background, well
+        // we are back active so skip it.
+        unscheduleGcIdler();
+
+        LoadedApk packageInfo = getPackageInfoNoCheck(
+                data.info.applicationInfo, data.compatInfo);
+        Service service = null;
+        try {
+            java.lang.ClassLoader cl = packageInfo.getClassLoader();
+            service = packageInfo.getAppFactory()
+                    .instantiateService(cl, data.info.name, data.intent);
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(service, e)) {
+                throw new RuntimeException(
+                    "Unable to instantiate service " + data.info.name
+                    + ": " + e.toString(), e);
+            }
+        }
+
+        try {
+            if (localLOGV) Slog.v(TAG, "Creating service " + data.info.name);
+
+            ContextImpl context = ContextImpl.createAppContext(this, packageInfo);
+            context.setOuterContext(service);
+
+            Application app = packageInfo.makeApplication(false, mInstrumentation);
+            service.attach(context, this, data.info.name, data.token, app,
+                    ActivityManager.getService());
+            service.onCreate();
+            mServices.put(data.token, service);
+            try {
+                ActivityManager.getService().serviceDoneExecuting(
+                        data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(service, e)) {
+                throw new RuntimeException(
+                    "Unable to create service " + data.info.name
+                    + ": " + e.toString(), e);
+            }
+        }
+    }
+
+    private void handleBindService(BindServiceData data) {
+        Service s = mServices.get(data.token);
+        if (DEBUG_SERVICE)
+            Slog.v(TAG, "handleBindService s=" + s + " rebind=" + data.rebind);
+        if (s != null) {
+            try {
+                data.intent.setExtrasClassLoader(s.getClassLoader());
+                data.intent.prepareToEnterProcess();
+                try {
+                    if (!data.rebind) {
+                        IBinder binder = s.onBind(data.intent);
+                        ActivityManager.getService().publishService(
+                                data.token, data.intent, binder);
+                    } else {
+                        s.onRebind(data.intent);
+                        ActivityManager.getService().serviceDoneExecuting(
+                                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+                    }
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to bind to service " + s
+                            + " with " + data.intent + ": " + e.toString(), e);
+                }
+            }
+        }
+    }
+
+    private void handleUnbindService(BindServiceData data) {
+        Service s = mServices.get(data.token);
+        if (s != null) {
+            try {
+                data.intent.setExtrasClassLoader(s.getClassLoader());
+                data.intent.prepareToEnterProcess();
+                boolean doRebind = s.onUnbind(data.intent);
+                try {
+                    if (doRebind) {
+                        ActivityManager.getService().unbindFinished(
+                                data.token, data.intent, doRebind);
+                    } else {
+                        ActivityManager.getService().serviceDoneExecuting(
+                                data.token, SERVICE_DONE_EXECUTING_ANON, 0, 0);
+                    }
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to unbind to service " + s
+                            + " with " + data.intent + ": " + e.toString(), e);
+                }
+            }
+        }
+    }
+
+    private void handleDumpService(DumpComponentInfo info) {
+        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+        try {
+            Service s = mServices.get(info.token);
+            if (s != null) {
+                PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+                        info.fd.getFileDescriptor()));
+                s.dump(info.fd.getFileDescriptor(), pw, info.args);
+                pw.flush();
+            }
+        } finally {
+            IoUtils.closeQuietly(info.fd);
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
+    private void handleDumpActivity(DumpComponentInfo info) {
+        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+        try {
+            ActivityClientRecord r = mActivities.get(info.token);
+            if (r != null && r.activity != null) {
+                PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+                        info.fd.getFileDescriptor()));
+                r.activity.dump(info.prefix, info.fd.getFileDescriptor(), pw, info.args);
+                pw.flush();
+            }
+        } finally {
+            IoUtils.closeQuietly(info.fd);
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
+    private void handleDumpProvider(DumpComponentInfo info) {
+        final StrictMode.ThreadPolicy oldPolicy = StrictMode.allowThreadDiskWrites();
+        try {
+            ProviderClientRecord r = mLocalProviders.get(info.token);
+            if (r != null && r.mLocalProvider != null) {
+                PrintWriter pw = new FastPrintWriter(new FileOutputStream(
+                        info.fd.getFileDescriptor()));
+                r.mLocalProvider.dump(info.fd.getFileDescriptor(), pw, info.args);
+                pw.flush();
+            }
+        } finally {
+            IoUtils.closeQuietly(info.fd);
+            StrictMode.setThreadPolicy(oldPolicy);
+        }
+    }
+
+    private void handleServiceArgs(ServiceArgsData data) {
+        Service s = mServices.get(data.token);
+        if (s != null) {
+            try {
+                if (data.args != null) {
+                    data.args.setExtrasClassLoader(s.getClassLoader());
+                    data.args.prepareToEnterProcess();
+                }
+                int res;
+                if (!data.taskRemoved) {
+                    res = s.onStartCommand(data.args, data.flags, data.startId);
+                } else {
+                    s.onTaskRemoved(data.args);
+                    res = Service.START_TASK_REMOVED_COMPLETE;
+                }
+
+                QueuedWork.waitToFinish();
+
+                try {
+                    ActivityManager.getService().serviceDoneExecuting(
+                            data.token, SERVICE_DONE_EXECUTING_START, data.startId, res);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to start service " + s
+                            + " with " + data.args + ": " + e.toString(), e);
+                }
+            }
+        }
+    }
+
+    private void handleStopService(IBinder token) {
+        Service s = mServices.remove(token);
+        if (s != null) {
+            try {
+                if (localLOGV) Slog.v(TAG, "Destroying service " + s);
+                s.onDestroy();
+                s.detachAndCleanUp();
+                Context context = s.getBaseContext();
+                if (context instanceof ContextImpl) {
+                    final String who = s.getClassName();
+                    ((ContextImpl) context).scheduleFinalCleanup(who, "Service");
+                }
+
+                QueuedWork.waitToFinish();
+
+                try {
+                    ActivityManager.getService().serviceDoneExecuting(
+                            token, SERVICE_DONE_EXECUTING_STOP, 0, 0);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(s, e)) {
+                    throw new RuntimeException(
+                            "Unable to stop service " + s
+                            + ": " + e.toString(), e);
+                }
+                Slog.i(TAG, "handleStopService: exception for " + token, e);
+            }
+        } else {
+            Slog.i(TAG, "handleStopService: token=" + token + " not found.");
+        }
+        //Slog.i(TAG, "Running services: " + mServices);
+    }
+
+    /**
+     * Resume the activity.
+     * @param token Target activity token.
+     * @param finalStateRequest Flag indicating if this is part of final state resolution for a
+     *                          transaction.
+     * @param reason Reason for performing the action.
+     *
+     * @return The {@link ActivityClientRecord} that was resumed, {@code null} otherwise.
+     */
+    @VisibleForTesting
+    public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
+            String reason) {
+        final ActivityClientRecord r = mActivities.get(token);
+        if (localLOGV) {
+            Slog.v(TAG, "Performing resume of " + r + " finished=" + r.activity.mFinished);
+        }
+        if (r == null || r.activity.mFinished) {
+            return null;
+        }
+        if (r.getLifecycleState() == ON_RESUME) {
+            if (!finalStateRequest) {
+                final RuntimeException e = new IllegalStateException(
+                        "Trying to resume activity which is already resumed");
+                Slog.e(TAG, e.getMessage(), e);
+                Slog.e(TAG, r.getStateString());
+                // TODO(lifecycler): A double resume request is possible when an activity
+                // receives two consequent transactions with relaunch requests and "resumed"
+                // final state requests and the second relaunch is omitted. We still try to
+                // handle two resume requests for the final state. For cases other than this
+                // one, we don't expect it to happen.
+            }
+            return null;
+        }
+        if (finalStateRequest) {
+            r.hideForNow = false;
+            r.activity.mStartedActivity = false;
+        }
+        try {
+            r.activity.onStateNotSaved();
+            r.activity.mFragments.noteStateNotSaved();
+            checkAndBlockForNetworkAccess();
+            if (r.pendingIntents != null) {
+                deliverNewIntents(r, r.pendingIntents);
+                r.pendingIntents = null;
+            }
+            if (r.pendingResults != null) {
+                deliverResults(r, r.pendingResults, reason);
+                r.pendingResults = null;
+            }
+            r.activity.performResume(r.startsNotResumed, reason);
+
+            r.state = null;
+            r.persistentState = null;
+            r.setState(ON_RESUME);
+
+            reportTopResumedActivityChanged(r, r.isTopResumedActivity, "topWhenResuming");
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(r.activity, e)) {
+                throw new RuntimeException("Unable to resume activity "
+                        + r.intent.getComponent().toShortString() + ": " + e.toString(), e);
+            }
+        }
+        return r;
+    }
+
+    static final void cleanUpPendingRemoveWindows(ActivityClientRecord r, boolean force) {
+        if (r.mPreserveWindow && !force) {
+            return;
+        }
+        if (r.mPendingRemoveWindow != null) {
+            r.mPendingRemoveWindowManager.removeViewImmediate(
+                    r.mPendingRemoveWindow.getDecorView());
+            IBinder wtoken = r.mPendingRemoveWindow.getDecorView().getWindowToken();
+            if (wtoken != null) {
+                WindowManagerGlobal.getInstance().closeAll(wtoken,
+                        r.activity.getClass().getName(), "Activity");
+            }
+        }
+        r.mPendingRemoveWindow = null;
+        r.mPendingRemoveWindowManager = null;
+    }
+
+    @Override
+    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
+            String reason) {
+        // If we are getting ready to gc after going to the background, well
+        // we are back active so skip it.
+        unscheduleGcIdler();
+        mSomeActivitiesChanged = true;
+
+        // TODO Push resumeArgs into the activity for consideration
+        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
+        if (r == null) {
+            // We didn't actually resume the activity, so skipping any follow-up actions.
+            return;
+        }
+        if (mActivitiesToBeDestroyed.containsKey(token)) {
+            // Although the activity is resumed, it is going to be destroyed. So the following
+            // UI operations are unnecessary and also prevents exception because its token may
+            // be gone that window manager cannot recognize it. All necessary cleanup actions
+            // performed below will be done while handling destruction.
+            return;
+        }
+
+        final Activity a = r.activity;
+
+        if (localLOGV) {
+            Slog.v(TAG, "Resume " + r + " started activity: " + a.mStartedActivity
+                    + ", hideForNow: " + r.hideForNow + ", finished: " + a.mFinished);
+        }
+
+        final int forwardBit = isForward
+                ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
+
+        // If the window hasn't yet been added to the window manager,
+        // and this guy didn't finish itself or start another activity,
+        // then go ahead and add the window.
+        boolean willBeVisible = !a.mStartedActivity;
+        if (!willBeVisible) {
+            try {
+                willBeVisible = ActivityTaskManager.getService().willActivityBeVisible(
+                        a.getActivityToken());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        if (r.window == null && !a.mFinished && willBeVisible) {
+            r.window = r.activity.getWindow();
+            View decor = r.window.getDecorView();
+            decor.setVisibility(View.INVISIBLE);
+            ViewManager wm = a.getWindowManager();
+            WindowManager.LayoutParams l = r.window.getAttributes();
+            a.mDecor = decor;
+            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
+            l.softInputMode |= forwardBit;
+            if (r.mPreserveWindow) {
+                a.mWindowAdded = true;
+                r.mPreserveWindow = false;
+                // Normally the ViewRoot sets up callbacks with the Activity
+                // in addView->ViewRootImpl#setView. If we are instead reusing
+                // the decor view we have to notify the view root that the
+                // callbacks may have changed.
+                ViewRootImpl impl = decor.getViewRootImpl();
+                if (impl != null) {
+                    impl.notifyChildRebuilt();
+                }
+            }
+            if (a.mVisibleFromClient) {
+                if (!a.mWindowAdded) {
+                    a.mWindowAdded = true;
+                    wm.addView(decor, l);
+                } else {
+                    // The activity will get a callback for this {@link LayoutParams} change
+                    // earlier. However, at that time the decor will not be set (this is set
+                    // in this method), so no action will be taken. This call ensures the
+                    // callback occurs with the decor set.
+                    a.onWindowAttributesChanged(l);
+                }
+            }
+
+            // If the window has already been added, but during resume
+            // we started another activity, then don't yet make the
+            // window visible.
+        } else if (!willBeVisible) {
+            if (localLOGV) Slog.v(TAG, "Launch " + r + " mStartedActivity set");
+            r.hideForNow = true;
+        }
+
+        // Get rid of anything left hanging around.
+        cleanUpPendingRemoveWindows(r, false /* force */);
+
+        // The window is now visible if it has been added, we are not
+        // simply finishing, and we are not starting another activity.
+        if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) {
+            if (r.newConfig != null) {
+                performConfigurationChangedForActivity(r, r.newConfig);
+                if (DEBUG_CONFIGURATION) {
+                    Slog.v(TAG, "Resuming activity " + r.activityInfo.name + " with newConfig "
+                            + r.activity.mCurrentConfig);
+                }
+                r.newConfig = null;
+            }
+            if (localLOGV) Slog.v(TAG, "Resuming " + r + " with isForward=" + isForward);
+            WindowManager.LayoutParams l = r.window.getAttributes();
+            if ((l.softInputMode
+                    & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)
+                    != forwardBit) {
+                l.softInputMode = (l.softInputMode
+                        & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION))
+                        | forwardBit;
+                if (r.activity.mVisibleFromClient) {
+                    ViewManager wm = a.getWindowManager();
+                    View decor = r.window.getDecorView();
+                    wm.updateViewLayout(decor, l);
+                }
+            }
+
+            r.activity.mVisibleFromServer = true;
+            mNumVisibleActivities++;
+            if (r.activity.mVisibleFromClient) {
+                r.activity.makeVisible();
+            }
+        }
+
+        r.nextIdle = mNewActivities;
+        mNewActivities = r;
+        if (localLOGV) Slog.v(TAG, "Scheduling idle handler for " + r);
+        Looper.myQueue().addIdleHandler(new Idler());
+    }
+
+
+    @Override
+    public void handleTopResumedActivityChanged(IBinder token, boolean onTop, String reason) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (r == null || r.activity == null) {
+            Slog.w(TAG, "Not found target activity to report position change for token: " + token);
+            return;
+        }
+
+        if (DEBUG_ORDER) {
+            Slog.d(TAG, "Received position change to top: " + onTop + " for activity: " + r);
+        }
+
+        if (r.isTopResumedActivity == onTop) {
+            throw new IllegalStateException("Activity top position already set to onTop=" + onTop);
+        }
+
+        r.isTopResumedActivity = onTop;
+
+        if (r.getLifecycleState() == ON_RESUME) {
+            reportTopResumedActivityChanged(r, onTop, "topStateChangedWhenResumed");
+        } else {
+            if (DEBUG_ORDER) {
+                Slog.d(TAG, "Won't deliver top position change in state=" + r.getLifecycleState());
+            }
+        }
+    }
+
+    /**
+     * Call {@link Activity#onTopResumedActivityChanged(boolean)} if its top resumed state changed
+     * since the last report.
+     */
+    private void reportTopResumedActivityChanged(ActivityClientRecord r, boolean onTop,
+            String reason) {
+        if (r.lastReportedTopResumedState != onTop) {
+            r.lastReportedTopResumedState = onTop;
+            r.activity.performTopResumedActivityChanged(onTop, reason);
+        }
+    }
+
+    @Override
+    public void handlePauseActivity(IBinder token, boolean finished, boolean userLeaving,
+            int configChanges, PendingTransactionActions pendingActions, String reason) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (r != null) {
+            if (userLeaving) {
+                performUserLeavingActivity(r);
+            }
+
+            r.activity.mConfigChangeFlags |= configChanges;
+            performPauseActivity(r, finished, reason, pendingActions);
+
+            // Make sure any pending writes are now committed.
+            if (r.isPreHoneycomb()) {
+                QueuedWork.waitToFinish();
+            }
+            mSomeActivitiesChanged = true;
+        }
+    }
+
+    final void performUserLeavingActivity(ActivityClientRecord r) {
+        mInstrumentation.callActivityOnUserLeaving(r.activity);
+    }
+
+    final Bundle performPauseActivity(IBinder token, boolean finished, String reason,
+            PendingTransactionActions pendingActions) {
+        ActivityClientRecord r = mActivities.get(token);
+        return r != null ? performPauseActivity(r, finished, reason, pendingActions) : null;
+    }
+
+    /**
+     * Pause the activity.
+     * @return Saved instance state for pre-Honeycomb apps if it was saved, {@code null} otherwise.
+     */
+    private Bundle performPauseActivity(ActivityClientRecord r, boolean finished, String reason,
+            PendingTransactionActions pendingActions) {
+        if (r.paused) {
+            if (r.activity.mFinished) {
+                // If we are finishing, we won't call onResume() in certain cases.
+                // So here we likewise don't want to call onPause() if the activity
+                // isn't resumed.
+                return null;
+            }
+            RuntimeException e = new RuntimeException(
+                    "Performing pause of activity that is not resumed: "
+                    + r.intent.getComponent().toShortString());
+            Slog.e(TAG, e.getMessage(), e);
+        }
+        if (finished) {
+            r.activity.mFinished = true;
+        }
+
+        // Pre-Honeycomb apps always save their state before pausing
+        final boolean shouldSaveState = !r.activity.mFinished && r.isPreHoneycomb();
+        if (shouldSaveState) {
+            callActivityOnSaveInstanceState(r);
+        }
+
+        performPauseActivityIfNeeded(r, reason);
+
+        // Notify any outstanding on paused listeners
+        ArrayList<OnActivityPausedListener> listeners;
+        synchronized (mOnPauseListeners) {
+            listeners = mOnPauseListeners.remove(r.activity);
+        }
+        int size = (listeners != null ? listeners.size() : 0);
+        for (int i = 0; i < size; i++) {
+            listeners.get(i).onPaused(r.activity);
+        }
+
+        final Bundle oldState = pendingActions != null ? pendingActions.getOldState() : null;
+        if (oldState != null) {
+            // We need to keep around the original state, in case we need to be created again.
+            // But we only do this for pre-Honeycomb apps, which always save their state when
+            // pausing, so we can not have them save their state when restarting from a paused
+            // state. For HC and later, we want to (and can) let the state be saved as the
+            // normal part of stopping the activity.
+            if (r.isPreHoneycomb()) {
+                r.state = oldState;
+            }
+        }
+
+        return shouldSaveState ? r.state : null;
+    }
+
+    private void performPauseActivityIfNeeded(ActivityClientRecord r, String reason) {
+        if (r.paused) {
+            // You are already paused silly...
+            return;
+        }
+
+        // Always reporting top resumed position loss when pausing an activity. If necessary, it
+        // will be restored in performResumeActivity().
+        reportTopResumedActivityChanged(r, false /* onTop */, "pausing");
+
+        try {
+            r.activity.mCalled = false;
+            mInstrumentation.callActivityOnPause(r.activity);
+            if (!r.activity.mCalled) {
+                throw new SuperNotCalledException("Activity " + safeToComponentShortString(r.intent)
+                        + " did not call through to super.onPause()");
+            }
+        } catch (SuperNotCalledException e) {
+            throw e;
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(r.activity, e)) {
+                throw new RuntimeException("Unable to pause activity "
+                        + safeToComponentShortString(r.intent) + ": " + e.toString(), e);
+            }
+        }
+        r.setState(ON_PAUSE);
+    }
+
+    /** Called from {@link LocalActivityManager}. */
+    @UnsupportedAppUsage
+    final void performStopActivity(IBinder token, boolean saveState, String reason) {
+        ActivityClientRecord r = mActivities.get(token);
+        performStopActivityInner(r, null /* stopInfo */, false /* keepShown */, saveState,
+                false /* finalStateRequest */, reason);
+    }
+
+    private static final class ProviderRefCount {
+        public final ContentProviderHolder holder;
+        public final ProviderClientRecord client;
+        public int stableCount;
+        public int unstableCount;
+
+        // When this is set, the stable and unstable ref counts are 0 and
+        // we have a pending operation scheduled to remove the ref count
+        // from the activity manager.  On the activity manager we are still
+        // holding an unstable ref, though it is not reflected in the counts
+        // here.
+        public boolean removePending;
+
+        ProviderRefCount(ContentProviderHolder inHolder,
+                ProviderClientRecord inClient, int sCount, int uCount) {
+            holder = inHolder;
+            client = inClient;
+            stableCount = sCount;
+            unstableCount = uCount;
+        }
+    }
+
+    /**
+     * Core implementation of stopping an activity.  Note this is a little
+     * tricky because the server's meaning of stop is slightly different
+     * than our client -- for the server, stop means to save state and give
+     * it the result when it is done, but the window may still be visible.
+     * For the client, we want to call onStop()/onStart() to indicate when
+     * the activity's UI visibility changes.
+     * @param r Target activity client record.
+     * @param info Action that will report activity stop to server.
+     * @param keepShown Flag indicating whether the activity is still shown.
+     * @param saveState Flag indicating whether the activity state should be saved.
+     * @param finalStateRequest Flag indicating if this call is handling final lifecycle state
+     *                          request for a transaction.
+     * @param reason Reason for performing this operation.
+     */
+    private void performStopActivityInner(ActivityClientRecord r, StopInfo info, boolean keepShown,
+            boolean saveState, boolean finalStateRequest, String reason) {
+        if (localLOGV) Slog.v(TAG, "Performing stop of " + r);
+        if (r != null) {
+            if (!keepShown && r.stopped) {
+                if (r.activity.mFinished) {
+                    // If we are finishing, we won't call onResume() in certain
+                    // cases.  So here we likewise don't want to call onStop()
+                    // if the activity isn't resumed.
+                    return;
+                }
+                if (!finalStateRequest) {
+                    final RuntimeException e = new RuntimeException(
+                            "Performing stop of activity that is already stopped: "
+                                    + r.intent.getComponent().toShortString());
+                    Slog.e(TAG, e.getMessage(), e);
+                    Slog.e(TAG, r.getStateString());
+                }
+            }
+
+            // One must first be paused before stopped...
+            performPauseActivityIfNeeded(r, reason);
+
+            if (info != null) {
+                try {
+                    // First create a thumbnail for the activity...
+                    // For now, don't create the thumbnail here; we are
+                    // doing that by doing a screen snapshot.
+                    info.setDescription(r.activity.onCreateDescription());
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to save state of activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+            }
+
+            if (!keepShown) {
+                callActivityOnStop(r, saveState, reason);
+            }
+        }
+    }
+
+    /**
+     * Calls {@link Activity#onStop()} and {@link Activity#onSaveInstanceState(Bundle)}, and updates
+     * the client record's state.
+     * All calls to stop an activity must be done through this method to make sure that
+     * {@link Activity#onSaveInstanceState(Bundle)} is also executed in the same call.
+     */
+    private void callActivityOnStop(ActivityClientRecord r, boolean saveState, String reason) {
+        // Before P onSaveInstanceState was called before onStop, starting with P it's
+        // called after. Before Honeycomb state was always saved before onPause.
+        final boolean shouldSaveState = saveState && !r.activity.mFinished && r.state == null
+                && !r.isPreHoneycomb();
+        final boolean isPreP = r.isPreP();
+        if (shouldSaveState && isPreP) {
+            callActivityOnSaveInstanceState(r);
+        }
+
+        try {
+            r.activity.performStop(r.mPreserveWindow, reason);
+        } catch (SuperNotCalledException e) {
+            throw e;
+        } catch (Exception e) {
+            if (!mInstrumentation.onException(r.activity, e)) {
+                throw new RuntimeException(
+                        "Unable to stop activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+            }
+        }
+        r.setState(ON_STOP);
+
+        if (shouldSaveState && !isPreP) {
+            callActivityOnSaveInstanceState(r);
+        }
+    }
+
+    private void updateVisibility(ActivityClientRecord r, boolean show) {
+        View v = r.activity.mDecor;
+        if (v != null) {
+            if (show) {
+                if (!r.activity.mVisibleFromServer) {
+                    r.activity.mVisibleFromServer = true;
+                    mNumVisibleActivities++;
+                    if (r.activity.mVisibleFromClient) {
+                        r.activity.makeVisible();
+                    }
+                }
+                if (r.newConfig != null) {
+                    performConfigurationChangedForActivity(r, r.newConfig);
+                    if (DEBUG_CONFIGURATION) Slog.v(TAG, "Updating activity vis "
+                            + r.activityInfo.name + " with new config "
+                            + r.activity.mCurrentConfig);
+                    r.newConfig = null;
+                }
+            } else {
+                if (r.activity.mVisibleFromServer) {
+                    r.activity.mVisibleFromServer = false;
+                    mNumVisibleActivities--;
+                    v.setVisibility(View.INVISIBLE);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void handleStopActivity(IBinder token, boolean show, int configChanges,
+            PendingTransactionActions pendingActions, boolean finalStateRequest, String reason) {
+        final ActivityClientRecord r = mActivities.get(token);
+        r.activity.mConfigChangeFlags |= configChanges;
+
+        final StopInfo stopInfo = new StopInfo();
+        performStopActivityInner(r, stopInfo, show, true /* saveState */, finalStateRequest,
+                reason);
+
+        if (localLOGV) Slog.v(
+            TAG, "Finishing stop of " + r + ": show=" + show
+            + " win=" + r.window);
+
+        updateVisibility(r, show);
+
+        // Make sure any pending writes are now committed.
+        if (!r.isPreHoneycomb()) {
+            QueuedWork.waitToFinish();
+        }
+
+        stopInfo.setActivity(r);
+        stopInfo.setState(r.state);
+        stopInfo.setPersistentState(r.persistentState);
+        pendingActions.setStopInfo(stopInfo);
+        mSomeActivitiesChanged = true;
+    }
+
+    /**
+     * Schedule the call to tell the activity manager we have stopped.  We don't do this
+     * immediately, because we want to have a chance for any other pending work (in particular
+     * memory trim requests) to complete before you tell the activity manager to proceed and allow
+     * us to go fully into the background.
+     */
+    @Override
+    public void reportStop(PendingTransactionActions pendingActions) {
+        mH.post(pendingActions.getStopInfo());
+    }
+
+    @Override
+    public void performRestartActivity(IBinder token, boolean start) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (r.stopped) {
+            r.activity.performRestart(start, "performRestartActivity");
+            if (start) {
+                r.setState(ON_START);
+            }
+        }
+    }
+
+    @Override
+    public void handleWindowVisibility(IBinder token, boolean show) {
+        ActivityClientRecord r = mActivities.get(token);
+
+        if (r == null) {
+            Log.w(TAG, "handleWindowVisibility: no activity for token " + token);
+            return;
+        }
+
+        if (!show && !r.stopped) {
+            performStopActivityInner(r, null /* stopInfo */, show, false /* saveState */,
+                    false /* finalStateRequest */, "handleWindowVisibility");
+        } else if (show && r.getLifecycleState() == ON_STOP) {
+            // If we are getting ready to gc after going to the background, well
+            // we are back active so skip it.
+            unscheduleGcIdler();
+
+            r.activity.performRestart(true /* start */, "handleWindowVisibility");
+            r.setState(ON_START);
+        }
+        if (r.activity.mDecor != null) {
+            if (false) Slog.v(
+                TAG, "Handle window " + r + " visibility: " + show);
+            updateVisibility(r, show);
+        }
+        mSomeActivitiesChanged = true;
+    }
+
+    // TODO: This method should be changed to use {@link #performStopActivityInner} to perform to
+    // stop operation on the activity to reduce code duplication and the chance of fixing a bug in
+    // one place and missing the other.
+    private void handleSleeping(IBinder token, boolean sleeping) {
+        ActivityClientRecord r = mActivities.get(token);
+
+        if (r == null) {
+            Log.w(TAG, "handleSleeping: no activity for token " + token);
+            return;
+        }
+
+        if (sleeping) {
+            if (!r.stopped && !r.isPreHoneycomb()) {
+                callActivityOnStop(r, true /* saveState */, "sleeping");
+            }
+
+            // Make sure any pending writes are now committed.
+            if (!r.isPreHoneycomb()) {
+                QueuedWork.waitToFinish();
+            }
+
+            // Tell activity manager we slept.
+            try {
+                ActivityTaskManager.getService().activitySlept(r.token);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        } else {
+            if (r.stopped && r.activity.mVisibleFromServer) {
+                r.activity.performRestart(true /* start */, "handleSleeping");
+                r.setState(ON_START);
+            }
+        }
+    }
+
+    private void handleSetCoreSettings(Bundle coreSettings) {
+        synchronized (mResourcesManager) {
+            mCoreSettings = coreSettings;
+        }
+        onCoreSettingsChange();
+    }
+
+    private void onCoreSettingsChange() {
+        if (updateDebugViewAttributeState()) {
+            // request all activities to relaunch for the changes to take place
+            relaunchAllActivities(false /* preserveWindows */);
+        }
+    }
+
+    private boolean updateDebugViewAttributeState() {
+        boolean previousState = View.sDebugViewAttributes;
+
+        View.sDebugViewAttributesApplicationPackage = mCoreSettings.getString(
+                Settings.Global.DEBUG_VIEW_ATTRIBUTES_APPLICATION_PACKAGE, "");
+        String currentPackage = (mBoundApplication != null && mBoundApplication.appInfo != null)
+                ? mBoundApplication.appInfo.packageName : "";
+        View.sDebugViewAttributes =
+                mCoreSettings.getInt(Settings.Global.DEBUG_VIEW_ATTRIBUTES, 0) != 0
+                        || View.sDebugViewAttributesApplicationPackage.equals(currentPackage);
+        return previousState != View.sDebugViewAttributes;
+    }
+
+    private void relaunchAllActivities(boolean preserveWindows) {
+        for (Map.Entry<IBinder, ActivityClientRecord> entry : mActivities.entrySet()) {
+            final ActivityClientRecord r = entry.getValue();
+            if (!r.activity.mFinished) {
+                if (preserveWindows && r.window != null) {
+                    r.mPreserveWindow = true;
+                }
+                scheduleRelaunchActivity(entry.getKey());
+            }
+        }
+    }
+
+    private void handleUpdatePackageCompatibilityInfo(UpdateCompatibilityData data) {
+        LoadedApk apk = peekPackageInfo(data.pkg, false);
+        if (apk != null) {
+            apk.setCompatibilityInfo(data.info);
+        }
+        apk = peekPackageInfo(data.pkg, true);
+        if (apk != null) {
+            apk.setCompatibilityInfo(data.info);
+        }
+        handleConfigurationChanged(mConfiguration, data.info);
+        WindowManagerGlobal.getInstance().reportNewConfiguration(mConfiguration);
+    }
+
+    private void deliverResults(ActivityClientRecord r, List<ResultInfo> results, String reason) {
+        final int N = results.size();
+        for (int i=0; i<N; i++) {
+            ResultInfo ri = results.get(i);
+            try {
+                if (ri.mData != null) {
+                    ri.mData.setExtrasClassLoader(r.activity.getClassLoader());
+                    ri.mData.prepareToEnterProcess();
+                }
+                if (DEBUG_RESULTS) Slog.v(TAG,
+                        "Delivering result to activity " + r + " : " + ri);
+                r.activity.dispatchActivityResult(ri.mResultWho,
+                        ri.mRequestCode, ri.mResultCode, ri.mData, reason);
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(r.activity, e)) {
+                    throw new RuntimeException(
+                            "Failure delivering result " + ri + " to activity "
+                            + r.intent.getComponent().toShortString()
+                            + ": " + e.toString(), e);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void handleSendResult(IBinder token, List<ResultInfo> results, String reason) {
+        ActivityClientRecord r = mActivities.get(token);
+        if (DEBUG_RESULTS) Slog.v(TAG, "Handling send result to " + r);
+        if (r != null) {
+            final boolean resumed = !r.paused;
+            if (!r.activity.mFinished && r.activity.mDecor != null
+                    && r.hideForNow && resumed) {
+                // We had hidden the activity because it started another
+                // one...  we have gotten a result back and we are not
+                // paused, so make sure our window is visible.
+                updateVisibility(r, true);
+            }
+            if (resumed) {
+                try {
+                    // Now we are idle.
+                    r.activity.mCalled = false;
+                    mInstrumentation.callActivityOnPause(r.activity);
+                    if (!r.activity.mCalled) {
+                        throw new SuperNotCalledException(
+                            "Activity " + r.intent.getComponent().toShortString()
+                            + " did not call through to super.onPause()");
+                    }
+                } catch (SuperNotCalledException e) {
+                    throw e;
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to pause activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+            }
+            checkAndBlockForNetworkAccess();
+            deliverResults(r, results, reason);
+            if (resumed) {
+                r.activity.performResume(false, reason);
+            }
+        }
+    }
+
+    /** Core implementation of activity destroy call. */
+    ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
+            int configChanges, boolean getNonConfigInstance, String reason) {
+        ActivityClientRecord r = mActivities.get(token);
+        Class<? extends Activity> activityClass = null;
+        if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
+        if (r != null) {
+            activityClass = r.activity.getClass();
+            r.activity.mConfigChangeFlags |= configChanges;
+            if (finishing) {
+                r.activity.mFinished = true;
+            }
+
+            performPauseActivityIfNeeded(r, "destroy");
+
+            if (!r.stopped) {
+                callActivityOnStop(r, false /* saveState */, "destroy");
+            }
+            if (getNonConfigInstance) {
+                try {
+                    r.lastNonConfigurationInstances
+                            = r.activity.retainNonConfigurationInstances();
+                } catch (Exception e) {
+                    if (!mInstrumentation.onException(r.activity, e)) {
+                        throw new RuntimeException(
+                                "Unable to retain activity "
+                                + r.intent.getComponent().toShortString()
+                                + ": " + e.toString(), e);
+                    }
+                }
+            }
+            try {
+                r.activity.mCalled = false;
+                mInstrumentation.callActivityOnDestroy(r.activity);
+                if (!r.activity.mCalled) {
+                    throw new SuperNotCalledException(
+                        "Activity " + safeToComponentShortString(r.intent) +
+                        " did not call through to super.onDestroy()");
+                }
+                if (r.window != null) {
+                    r.window.closeAllPanels();
+                }
+            } catch (SuperNotCalledException e) {
+                throw e;
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(r.activity, e)) {
+                    throw new RuntimeException(
+                            "Unable to destroy activity " + safeToComponentShortString(r.intent)
+                            + ": " + e.toString(), e);
+                }
+            }
+            r.setState(ON_DESTROY);
+        }
+        schedulePurgeIdler();
+        // updatePendingActivityConfiguration() reads from mActivities to update
+        // ActivityClientRecord which runs in a different thread. Protect modifications to
+        // mActivities to avoid race.
+        synchronized (mResourcesManager) {
+            mActivities.remove(token);
+        }
+        StrictMode.decrementExpectedActivityCount(activityClass);
+        return r;
+    }
+
+    private static String safeToComponentShortString(Intent intent) {
+        ComponentName component = intent.getComponent();
+        return component == null ? "[Unknown]" : component.toShortString();
+    }
+
+    @Override
+    public Map<IBinder, ClientTransactionItem> getActivitiesToBeDestroyed() {
+        return mActivitiesToBeDestroyed;
+    }
+
+    @Override
+    public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
+            boolean getNonConfigInstance, String reason) {
+        ActivityClientRecord r = performDestroyActivity(token, finishing,
+                configChanges, getNonConfigInstance, reason);
+        if (r != null) {
+            cleanUpPendingRemoveWindows(r, finishing);
+            WindowManager wm = r.activity.getWindowManager();
+            View v = r.activity.mDecor;
+            if (v != null) {
+                if (r.activity.mVisibleFromServer) {
+                    mNumVisibleActivities--;
+                }
+                IBinder wtoken = v.getWindowToken();
+                if (r.activity.mWindowAdded) {
+                    if (r.mPreserveWindow) {
+                        // Hold off on removing this until the new activity's
+                        // window is being added.
+                        r.mPendingRemoveWindow = r.window;
+                        r.mPendingRemoveWindowManager = wm;
+                        // We can only keep the part of the view hierarchy that we control,
+                        // everything else must be removed, because it might not be able to
+                        // behave properly when activity is relaunching.
+                        r.window.clearContentView();
+                    } else {
+                        wm.removeViewImmediate(v);
+                    }
+                }
+                if (wtoken != null && r.mPendingRemoveWindow == null) {
+                    WindowManagerGlobal.getInstance().closeAll(wtoken,
+                            r.activity.getClass().getName(), "Activity");
+                } else if (r.mPendingRemoveWindow != null) {
+                    // We're preserving only one window, others should be closed so app views
+                    // will be detached before the final tear down. It should be done now because
+                    // some components (e.g. WebView) rely on detach callbacks to perform receiver
+                    // unregister and other cleanup.
+                    WindowManagerGlobal.getInstance().closeAllExceptView(token, v,
+                            r.activity.getClass().getName(), "Activity");
+                }
+                r.activity.mDecor = null;
+            }
+            if (r.mPendingRemoveWindow == null) {
+                // If we are delaying the removal of the activity window, then
+                // we can't clean up all windows here.  Note that we can't do
+                // so later either, which means any windows that aren't closed
+                // by the app will leak.  Well we try to warning them a lot
+                // about leaking windows, because that is a bug, so if they are
+                // using this recreate facility then they get to live with leaks.
+                WindowManagerGlobal.getInstance().closeAll(token,
+                        r.activity.getClass().getName(), "Activity");
+            }
+
+            // Mocked out contexts won't be participating in the normal
+            // process lifecycle, but if we're running with a proper
+            // ApplicationContext we need to have it tear down things
+            // cleanly.
+            Context c = r.activity.getBaseContext();
+            if (c instanceof ContextImpl) {
+                ((ContextImpl) c).scheduleFinalCleanup(
+                        r.activity.getClass().getName(), "Activity");
+            }
+        }
+        if (finishing) {
+            try {
+                ActivityTaskManager.getService().activityDestroyed(token);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+        }
+        mSomeActivitiesChanged = true;
+    }
+
+    @Override
+    public ActivityClientRecord prepareRelaunchActivity(IBinder token,
+            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
+            int configChanges, MergedConfiguration config, boolean preserveWindow) {
+        ActivityClientRecord target = null;
+        boolean scheduleRelaunch = false;
+
+        synchronized (mResourcesManager) {
+            for (int i=0; i<mRelaunchingActivities.size(); i++) {
+                ActivityClientRecord r = mRelaunchingActivities.get(i);
+                if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: " + this + ", trying: " + r);
+                if (r.token == token) {
+                    target = r;
+                    if (pendingResults != null) {
+                        if (r.pendingResults != null) {
+                            r.pendingResults.addAll(pendingResults);
+                        } else {
+                            r.pendingResults = pendingResults;
+                        }
+                    }
+                    if (pendingNewIntents != null) {
+                        if (r.pendingIntents != null) {
+                            r.pendingIntents.addAll(pendingNewIntents);
+                        } else {
+                            r.pendingIntents = pendingNewIntents;
+                        }
+                    }
+                    break;
+                }
+            }
+
+            if (target == null) {
+                if (DEBUG_ORDER) Slog.d(TAG, "requestRelaunchActivity: target is null");
+                target = new ActivityClientRecord();
+                target.token = token;
+                target.pendingResults = pendingResults;
+                target.pendingIntents = pendingNewIntents;
+                target.mPreserveWindow = preserveWindow;
+                mRelaunchingActivities.add(target);
+                scheduleRelaunch = true;
+            }
+            target.createdConfig = config.getGlobalConfiguration();
+            target.overrideConfig = config.getOverrideConfiguration();
+            target.pendingConfigChanges |= configChanges;
+        }
+
+        return scheduleRelaunch ? target : null;
+    }
+
+    @Override
+    public void handleRelaunchActivity(ActivityClientRecord tmp,
+            PendingTransactionActions pendingActions) {
+        // If we are getting ready to gc after going to the background, well
+        // we are back active so skip it.
+        unscheduleGcIdler();
+        mSomeActivitiesChanged = true;
+
+        Configuration changedConfig = null;
+        int configChanges = 0;
+
+        // First: make sure we have the most recent configuration and most
+        // recent version of the activity, or skip it if some previous call
+        // had taken a more recent version.
+        synchronized (mResourcesManager) {
+            int N = mRelaunchingActivities.size();
+            IBinder token = tmp.token;
+            tmp = null;
+            for (int i=0; i<N; i++) {
+                ActivityClientRecord r = mRelaunchingActivities.get(i);
+                if (r.token == token) {
+                    tmp = r;
+                    configChanges |= tmp.pendingConfigChanges;
+                    mRelaunchingActivities.remove(i);
+                    i--;
+                    N--;
+                }
+            }
+
+            if (tmp == null) {
+                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Abort, activity not relaunching!");
+                return;
+            }
+
+            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
+                    + tmp.token + " with configChanges=0x"
+                    + Integer.toHexString(configChanges));
+
+            if (mPendingConfiguration != null) {
+                changedConfig = mPendingConfiguration;
+                mPendingConfiguration = null;
+            }
+        }
+
+        if (tmp.createdConfig != null) {
+            // If the activity manager is passing us its current config,
+            // assume that is really what we want regardless of what we
+            // may have pending.
+            if (mConfiguration == null
+                    || (tmp.createdConfig.isOtherSeqNewer(mConfiguration)
+                            && mConfiguration.diff(tmp.createdConfig) != 0)) {
+                if (changedConfig == null
+                        || tmp.createdConfig.isOtherSeqNewer(changedConfig)) {
+                    changedConfig = tmp.createdConfig;
+                }
+            }
+        }
+
+        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Relaunching activity "
+                + tmp.token + ": changedConfig=" + changedConfig);
+
+        // If there was a pending configuration change, execute it first.
+        if (changedConfig != null) {
+            mCurDefaultDisplayDpi = changedConfig.densityDpi;
+            updateDefaultDensity();
+            handleConfigurationChanged(changedConfig, null);
+        }
+
+        ActivityClientRecord r = mActivities.get(tmp.token);
+        if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handling relaunch of " + r);
+        if (r == null) {
+            return;
+        }
+
+        r.activity.mConfigChangeFlags |= configChanges;
+        r.mPreserveWindow = tmp.mPreserveWindow;
+
+        r.activity.mChangingConfigurations = true;
+
+        // If we are preserving the main window across relaunches we would also like to preserve
+        // the children. However the client side view system does not support preserving
+        // the child views so we notify the window manager to expect these windows to
+        // be replaced and defer requests to destroy or hide them. This way we can achieve
+        // visual continuity. It's important that we do this here prior to pause and destroy
+        // as that is when we may hide or remove the child views.
+        //
+        // There is another scenario, if we have decided locally to relaunch the app from a
+        // call to recreate, then none of the windows will be prepared for replacement or
+        // preserved by the server, so we want to notify it that we are preparing to replace
+        // everything
+        try {
+            if (r.mPreserveWindow) {
+                WindowManagerGlobal.getWindowSession().prepareToReplaceWindows(
+                        r.token, true /* childrenOnly */);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
+                pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
+
+        if (pendingActions != null) {
+            // Only report a successful relaunch to WindowManager.
+            pendingActions.setReportRelaunchToWindowManager(true);
+        }
+    }
+
+    /**
+     * Post a message to relaunch the activity. We do this instead of launching it immediately,
+     * because this will destroy the activity from which it was called and interfere with the
+     * lifecycle changes it was going through before. We need to make sure that we have finished
+     * handling current transaction item before relaunching the activity.
+     */
+    void scheduleRelaunchActivity(IBinder token) {
+        mH.removeMessages(H.RELAUNCH_ACTIVITY, token);
+        sendMessage(H.RELAUNCH_ACTIVITY, token);
+    }
+
+    /** Performs the activity relaunch locally vs. requesting from system-server. */
+    private void handleRelaunchActivityLocally(IBinder token) {
+        final ActivityClientRecord r = mActivities.get(token);
+        if (r == null) {
+            Log.w(TAG, "Activity to relaunch no longer exists");
+            return;
+        }
+
+        final int prevState = r.getLifecycleState();
+
+        if (prevState < ON_RESUME || prevState > ON_STOP) {
+            Log.w(TAG, "Activity state must be in [ON_RESUME..ON_STOP] in order to be relaunched,"
+                    + "current state is " + prevState);
+            return;
+        }
+
+
+        // Initialize a relaunch request.
+        final MergedConfiguration mergedConfiguration = new MergedConfiguration(
+                r.createdConfig != null ? r.createdConfig : mConfiguration,
+                r.overrideConfig);
+        final ActivityRelaunchItem activityRelaunchItem = ActivityRelaunchItem.obtain(
+                null /* pendingResults */, null /* pendingIntents */, 0 /* configChanges */,
+                mergedConfiguration, r.mPreserveWindow);
+        // Make sure to match the existing lifecycle state in the end of the transaction.
+        final ActivityLifecycleItem lifecycleRequest =
+                TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
+        // Schedule the transaction.
+        final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
+        transaction.addCallback(activityRelaunchItem);
+        transaction.setLifecycleStateRequest(lifecycleRequest);
+        executeTransaction(transaction);
+    }
+
+    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
+            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
+            PendingTransactionActions pendingActions, boolean startsNotResumed,
+            Configuration overrideConfig, String reason) {
+        // Preserve last used intent, it may be set from Activity#setIntent().
+        final Intent customIntent = r.activity.mIntent;
+        // Need to ensure state is saved.
+        if (!r.paused) {
+            performPauseActivity(r, false, reason, null /* pendingActions */);
+        }
+        if (!r.stopped) {
+            callActivityOnStop(r, true /* saveState */, reason);
+        }
+
+        handleDestroyActivity(r.token, false, configChanges, true, reason);
+
+        r.activity = null;
+        r.window = null;
+        r.hideForNow = false;
+        r.nextIdle = null;
+        // Merge any pending results and pending intents; don't just replace them
+        if (pendingResults != null) {
+            if (r.pendingResults == null) {
+                r.pendingResults = pendingResults;
+            } else {
+                r.pendingResults.addAll(pendingResults);
+            }
+        }
+        if (pendingIntents != null) {
+            if (r.pendingIntents == null) {
+                r.pendingIntents = pendingIntents;
+            } else {
+                r.pendingIntents.addAll(pendingIntents);
+            }
+        }
+        r.startsNotResumed = startsNotResumed;
+        r.overrideConfig = overrideConfig;
+
+        handleLaunchActivity(r, pendingActions, customIntent);
+    }
+
+    @Override
+    public void reportRelaunch(IBinder token, PendingTransactionActions pendingActions) {
+        try {
+            ActivityTaskManager.getService().activityRelaunched(token);
+            final ActivityClientRecord r = mActivities.get(token);
+            if (pendingActions.shouldReportRelaunchToWindowManager() && r != null
+                    && r.window != null) {
+                r.window.reportActivityRelaunched();
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    private void callActivityOnSaveInstanceState(ActivityClientRecord r) {
+        r.state = new Bundle();
+        r.state.setAllowFds(false);
+        if (r.isPersistable()) {
+            r.persistentState = new PersistableBundle();
+            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state,
+                    r.persistentState);
+        } else {
+            mInstrumentation.callActivityOnSaveInstanceState(r.activity, r.state);
+        }
+    }
+
+    ArrayList<ComponentCallbacks2> collectComponentCallbacks(
+            boolean allActivities, Configuration newConfig) {
+        ArrayList<ComponentCallbacks2> callbacks
+                = new ArrayList<ComponentCallbacks2>();
+
+        synchronized (mResourcesManager) {
+            final int NAPP = mAllApplications.size();
+            for (int i=0; i<NAPP; i++) {
+                callbacks.add(mAllApplications.get(i));
+            }
+            final int NACT = mActivities.size();
+            for (int i=0; i<NACT; i++) {
+                ActivityClientRecord ar = mActivities.valueAt(i);
+                Activity a = ar.activity;
+                if (a != null) {
+                    Configuration thisConfig = applyConfigCompatMainThread(
+                            mCurDefaultDisplayDpi, newConfig,
+                            ar.packageInfo.getCompatibilityInfo());
+                    if (!ar.activity.mFinished && (allActivities || !ar.paused)) {
+                        // If the activity is currently resumed, its configuration
+                        // needs to change right now.
+                        callbacks.add(a);
+                    } else if (thisConfig != null) {
+                        // Otherwise, we will tell it about the change
+                        // the next time it is resumed or shown.  Note that
+                        // the activity manager may, before then, decide the
+                        // activity needs to be destroyed to handle its new
+                        // configuration.
+                        if (DEBUG_CONFIGURATION) {
+                            Slog.v(TAG, "Setting activity "
+                                    + ar.activityInfo.name + " newConfig=" + thisConfig);
+                        }
+                        ar.newConfig = thisConfig;
+                    }
+                }
+            }
+            final int NSVC = mServices.size();
+            for (int i=0; i<NSVC; i++) {
+                callbacks.add(mServices.valueAt(i));
+            }
+        }
+        synchronized (mProviderMap) {
+            final int NPRV = mLocalProviders.size();
+            for (int i=0; i<NPRV; i++) {
+                callbacks.add(mLocalProviders.valueAt(i).mLocalProvider);
+            }
+        }
+
+        return callbacks;
+    }
+
+    /**
+     * Updates the configuration for an Activity. The ActivityClientRecord's
+     * {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
+     * that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
+     * the updated Configuration.
+     * @param r ActivityClientRecord representing the Activity.
+     * @param newBaseConfig The new configuration to use. This may be augmented with
+     *                      {@link ActivityClientRecord#overrideConfig}.
+     */
+    private void performConfigurationChangedForActivity(ActivityClientRecord r,
+            Configuration newBaseConfig) {
+        performConfigurationChangedForActivity(r, newBaseConfig,
+                r.activity.getDisplayId(), false /* movedToDifferentDisplay */);
+    }
+
+    /**
+     * Updates the configuration for an Activity. The ActivityClientRecord's
+     * {@link ActivityClientRecord#overrideConfig} is used to compute the final Configuration for
+     * that Activity. {@link ActivityClientRecord#tmpConfig} is used as a temporary for delivering
+     * the updated Configuration.
+     * @param r ActivityClientRecord representing the Activity.
+     * @param newBaseConfig The new configuration to use. This may be augmented with
+     *                      {@link ActivityClientRecord#overrideConfig}.
+     * @param displayId The id of the display where the Activity currently resides.
+     * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+     * @return {@link Configuration} instance sent to client, null if not sent.
+     */
+    private Configuration performConfigurationChangedForActivity(ActivityClientRecord r,
+            Configuration newBaseConfig, int displayId, boolean movedToDifferentDisplay) {
+        r.tmpConfig.setTo(newBaseConfig);
+        if (r.overrideConfig != null) {
+            r.tmpConfig.updateFrom(r.overrideConfig);
+        }
+        final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
+                r.tmpConfig, r.overrideConfig, displayId, movedToDifferentDisplay);
+        freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
+        return reportedConfig;
+    }
+
+    /**
+     * Creates a new Configuration only if override would modify base. Otherwise returns base.
+     * @param base The base configuration.
+     * @param override The update to apply to the base configuration. Can be null.
+     * @return A Configuration representing base with override applied.
+     */
+    private static Configuration createNewConfigAndUpdateIfNotNull(@NonNull Configuration base,
+            @Nullable Configuration override) {
+        if (override == null) {
+            return base;
+        }
+        Configuration newConfig = new Configuration(base);
+        newConfig.updateFrom(override);
+        return newConfig;
+    }
+
+    /**
+     * Decides whether to update a component's configuration and whether to inform it.
+     * @param cb The component callback to notify of configuration change.
+     * @param newConfig The new configuration.
+     */
+    private void performConfigurationChanged(ComponentCallbacks2 cb, Configuration newConfig) {
+        if (!REPORT_TO_ACTIVITY) {
+            return;
+        }
+
+        // ContextThemeWrappers may override the configuration for that context. We must check and
+        // apply any overrides defined.
+        Configuration contextThemeWrapperOverrideConfig = null;
+        if (cb instanceof ContextThemeWrapper) {
+            final ContextThemeWrapper contextThemeWrapper = (ContextThemeWrapper) cb;
+            contextThemeWrapperOverrideConfig = contextThemeWrapper.getOverrideConfiguration();
+        }
+
+        // Apply the ContextThemeWrapper override if necessary.
+        // NOTE: Make sure the configurations are not modified, as they are treated as immutable
+        // in many places.
+        final Configuration configToReport = createNewConfigAndUpdateIfNotNull(
+                newConfig, contextThemeWrapperOverrideConfig);
+        cb.onConfigurationChanged(configToReport);
+    }
+
+    /**
+     * Decides whether to update an Activity's configuration and whether to inform it.
+     * @param activity The activity to notify of configuration change.
+     * @param newConfig The new configuration.
+     * @param amOverrideConfig The override config that differentiates the Activity's configuration
+     *                         from the base global configuration. This is supplied by
+     *                         ActivityManager.
+     * @param displayId Id of the display where activity currently resides.
+     * @param movedToDifferentDisplay Indicates if the activity was moved to different display.
+     * @return Configuration sent to client, null if no changes and not moved to different display.
+     */
+    private Configuration performActivityConfigurationChanged(Activity activity,
+            Configuration newConfig, Configuration amOverrideConfig, int displayId,
+            boolean movedToDifferentDisplay) {
+        if (activity == null) {
+            throw new IllegalArgumentException("No activity provided.");
+        }
+        final IBinder activityToken = activity.getActivityToken();
+        if (activityToken == null) {
+            throw new IllegalArgumentException("Activity token not set. Is the activity attached?");
+        }
+
+        boolean shouldChangeConfig = false;
+        if (activity.mCurrentConfig == null) {
+            shouldChangeConfig = true;
+        } else {
+            // If the new config is the same as the config this Activity is already running with and
+            // the override config also didn't change, then don't bother calling
+            // onConfigurationChanged.
+            final int diff = activity.mCurrentConfig.diffPublicOnly(newConfig);
+
+            if (diff != 0 || !mResourcesManager.isSameResourcesOverrideConfig(activityToken,
+                    amOverrideConfig)) {
+                // Always send the task-level config changes. For system-level configuration, if
+                // this activity doesn't handle any of the config changes, then don't bother
+                // calling onConfigurationChanged as we're going to destroy it.
+                if (!mUpdatingSystemConfig
+                        || (~activity.mActivityInfo.getRealConfigChanged() & diff) == 0
+                        || !REPORT_TO_ACTIVITY) {
+                    shouldChangeConfig = true;
+                }
+            }
+        }
+        if (!shouldChangeConfig && !movedToDifferentDisplay) {
+            // Nothing significant, don't proceed with updating and reporting.
+            return null;
+        }
+
+        // Propagate the configuration change to ResourcesManager and Activity.
+
+        // ContextThemeWrappers may override the configuration for that context. We must check and
+        // apply any overrides defined.
+        Configuration contextThemeWrapperOverrideConfig = activity.getOverrideConfiguration();
+
+        // We only update an Activity's configuration if this is not a global configuration change.
+        // This must also be done before the callback, or else we violate the contract that the new
+        // resources are available in ComponentCallbacks2#onConfigurationChanged(Configuration).
+        // Also apply the ContextThemeWrapper override if necessary.
+        // NOTE: Make sure the configurations are not modified, as they are treated as immutable in
+        // many places.
+        final Configuration finalOverrideConfig = createNewConfigAndUpdateIfNotNull(
+                amOverrideConfig, contextThemeWrapperOverrideConfig);
+        mResourcesManager.updateResourcesForActivity(activityToken, finalOverrideConfig,
+                displayId, movedToDifferentDisplay);
+
+        activity.mConfigChangeFlags = 0;
+        activity.mCurrentConfig = new Configuration(newConfig);
+
+        // Apply the ContextThemeWrapper override if necessary.
+        // NOTE: Make sure the configurations are not modified, as they are treated as immutable
+        // in many places.
+        final Configuration configToReport = createNewConfigAndUpdateIfNotNull(newConfig,
+                contextThemeWrapperOverrideConfig);
+
+        if (!REPORT_TO_ACTIVITY) {
+            // Not configured to report to activity.
+            return configToReport;
+        }
+
+        if (movedToDifferentDisplay) {
+            activity.dispatchMovedToDisplay(displayId, configToReport);
+        }
+
+        if (shouldChangeConfig) {
+            activity.mCalled = false;
+            activity.onConfigurationChanged(configToReport);
+            if (!activity.mCalled) {
+                throw new SuperNotCalledException("Activity " + activity.getLocalClassName() +
+                                " did not call through to super.onConfigurationChanged()");
+            }
+        }
+
+        return configToReport;
+    }
+
+    public final void applyConfigurationToResources(Configuration config) {
+        synchronized (mResourcesManager) {
+            mResourcesManager.applyConfigurationToResourcesLocked(config, null);
+        }
+    }
+
+    final Configuration applyCompatConfiguration(int displayDensity) {
+        Configuration config = mConfiguration;
+        if (mCompatConfiguration == null) {
+            mCompatConfiguration = new Configuration();
+        }
+        mCompatConfiguration.setTo(mConfiguration);
+        if (mResourcesManager.applyCompatConfigurationLocked(displayDensity,
+                mCompatConfiguration)) {
+            config = mCompatConfiguration;
+        }
+        return config;
+    }
+
+    @Override
+    public void handleConfigurationChanged(Configuration config) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "configChanged");
+        mCurDefaultDisplayDpi = config.densityDpi;
+        mUpdatingSystemConfig = true;
+        try {
+            handleConfigurationChanged(config, null /* compat */);
+        } finally {
+            mUpdatingSystemConfig = false;
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    private void handleConfigurationChanged(Configuration config, CompatibilityInfo compat) {
+
+        int configDiff;
+        boolean equivalent;
+
+        final Theme systemTheme = getSystemContext().getTheme();
+        final Theme systemUiTheme = getSystemUiContext().getTheme();
+
+        synchronized (mResourcesManager) {
+            if (mPendingConfiguration != null) {
+                if (!mPendingConfiguration.isOtherSeqNewer(config)) {
+                    config = mPendingConfiguration;
+                    mCurDefaultDisplayDpi = config.densityDpi;
+                    updateDefaultDensity();
+                }
+                mPendingConfiguration = null;
+            }
+
+            if (config == null) {
+                return;
+            }
+
+            // This flag tracks whether the new configuration is fundamentally equivalent to the
+            // existing configuration. This is necessary to determine whether non-activity callbacks
+            // should receive notice when the only changes are related to non-public fields.
+            // We do not gate calling {@link #performActivityConfigurationChanged} based on this
+            // flag as that method uses the same check on the activity config override as well.
+            equivalent = mConfiguration != null && (0 == mConfiguration.diffPublicOnly(config));
+
+            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle configuration changed: "
+                    + config);
+
+            mResourcesManager.applyConfigurationToResourcesLocked(config, compat);
+            updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
+                    mResourcesManager.getConfiguration().getLocales());
+
+            if (mConfiguration == null) {
+                mConfiguration = new Configuration();
+            }
+            if (!mConfiguration.isOtherSeqNewer(config) && compat == null) {
+                return;
+            }
+
+            configDiff = mConfiguration.updateFrom(config);
+            config = applyCompatConfiguration(mCurDefaultDisplayDpi);
+
+            if ((systemTheme.getChangingConfigurations() & configDiff) != 0) {
+                systemTheme.rebase();
+            }
+
+            if ((systemUiTheme.getChangingConfigurations() & configDiff) != 0) {
+                systemUiTheme.rebase();
+            }
+        }
+
+        ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(false, config);
+
+        freeTextLayoutCachesIfNeeded(configDiff);
+
+        if (callbacks != null) {
+            final int N = callbacks.size();
+            for (int i=0; i<N; i++) {
+                ComponentCallbacks2 cb = callbacks.get(i);
+                if (cb instanceof Activity) {
+                    // If callback is an Activity - call corresponding method to consider override
+                    // config and avoid onConfigurationChanged if it hasn't changed.
+                    Activity a = (Activity) cb;
+                    performConfigurationChangedForActivity(mActivities.get(a.getActivityToken()),
+                            config);
+                } else if (!equivalent) {
+                    performConfigurationChanged(cb, config);
+                }
+            }
+        }
+    }
+
+    /**
+     * Updates the application info.
+     *
+     * This only works in the system process. Must be called on the main thread.
+     */
+    public void handleSystemApplicationInfoChanged(@NonNull ApplicationInfo ai) {
+        Preconditions.checkState(mSystemThread, "Must only be called in the system process");
+        handleApplicationInfoChanged(ai);
+    }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    public void handleApplicationInfoChanged(@NonNull final ApplicationInfo ai) {
+        // Updates triggered by package installation go through a package update
+        // receiver. Here we try to capture ApplicationInfo changes that are
+        // caused by other sources, such as overlays. That means we want to be as conservative
+        // about code changes as possible. Take the diff of the old ApplicationInfo and the new
+        // to see if anything needs to change.
+        LoadedApk apk;
+        LoadedApk resApk;
+        // Update all affected loaded packages with new package information
+        synchronized (mResourcesManager) {
+            WeakReference<LoadedApk> ref = mPackages.get(ai.packageName);
+            apk = ref != null ? ref.get() : null;
+            ref = mResourcePackages.get(ai.packageName);
+            resApk = ref != null ? ref.get() : null;
+        }
+
+        final String[] oldResDirs = new String[2];
+
+        if (apk != null) {
+            oldResDirs[0] = apk.getResDir();
+            final ArrayList<String> oldPaths = new ArrayList<>();
+            LoadedApk.makePaths(this, apk.getApplicationInfo(), oldPaths);
+            apk.updateApplicationInfo(ai, oldPaths);
+        }
+        if (resApk != null) {
+            oldResDirs[1] = resApk.getResDir();
+            final ArrayList<String> oldPaths = new ArrayList<>();
+            LoadedApk.makePaths(this, resApk.getApplicationInfo(), oldPaths);
+            resApk.updateApplicationInfo(ai, oldPaths);
+        }
+
+        synchronized (mResourcesManager) {
+            // Update all affected Resources objects to use new ResourcesImpl
+            mResourcesManager.applyNewResourceDirsLocked(ai, oldResDirs);
+        }
+
+        ApplicationPackageManager.configurationChanged();
+
+        // Trigger a regular Configuration change event, only with a different assetsSeq number
+        // so that we actually call through to all components.
+        // TODO(adamlesinski): Change this to make use of ActivityManager's upcoming ability to
+        // store configurations per-process.
+        Configuration newConfig = new Configuration();
+        newConfig.assetsSeq = (mConfiguration != null ? mConfiguration.assetsSeq : 0) + 1;
+        handleConfigurationChanged(newConfig, null);
+
+        // Preserve windows to avoid black flickers when overlays change.
+        relaunchAllActivities(true /* preserveWindows */);
+    }
+
+    static void freeTextLayoutCachesIfNeeded(int configDiff) {
+        if (configDiff != 0) {
+            // Ask text layout engine to free its caches if there is a locale change
+            boolean hasLocaleConfigChange = ((configDiff & ActivityInfo.CONFIG_LOCALE) != 0);
+            if (hasLocaleConfigChange) {
+                Canvas.freeTextLayoutCaches();
+                if (DEBUG_CONFIGURATION) Slog.v(TAG, "Cleared TextLayout Caches");
+            }
+        }
+    }
+
+    @Override
+    public void updatePendingActivityConfiguration(IBinder activityToken,
+            Configuration overrideConfig) {
+        final ActivityClientRecord r;
+        synchronized (mResourcesManager) {
+            r = mActivities.get(activityToken);
+        }
+
+        if (r == null) {
+            if (DEBUG_CONFIGURATION) {
+                Slog.w(TAG, "Not found target activity to update its pending config.");
+            }
+            return;
+        }
+
+        synchronized (r) {
+            r.mPendingOverrideConfig = overrideConfig;
+        }
+    }
+
+    /**
+     * Handle new activity configuration and/or move to a different display.
+     * @param activityToken Target activity token.
+     * @param overrideConfig Activity override config.
+     * @param displayId Id of the display where activity was moved to, -1 if there was no move and
+     *                  value didn't change.
+     */
+    @Override
+    public void handleActivityConfigurationChanged(IBinder activityToken,
+            Configuration overrideConfig, int displayId) {
+        ActivityClientRecord r = mActivities.get(activityToken);
+        // Check input params.
+        if (r == null || r.activity == null) {
+            if (DEBUG_CONFIGURATION) Slog.w(TAG, "Not found target activity to report to: " + r);
+            return;
+        }
+        final boolean movedToDifferentDisplay = displayId != INVALID_DISPLAY
+                && displayId != r.activity.getDisplayId();
+
+        synchronized (r) {
+            if (r.mPendingOverrideConfig != null
+                    && !r.mPendingOverrideConfig.isOtherSeqNewer(overrideConfig)) {
+                overrideConfig = r.mPendingOverrideConfig;
+            }
+            r.mPendingOverrideConfig = null;
+        }
+
+        if (r.overrideConfig != null && !r.overrideConfig.isOtherSeqNewer(overrideConfig)
+                && !movedToDifferentDisplay) {
+            if (DEBUG_CONFIGURATION) {
+                Slog.v(TAG, "Activity already handled newer configuration so drop this"
+                        + " transaction. overrideConfig=" + overrideConfig + " r.overrideConfig="
+                        + r.overrideConfig);
+            }
+            return;
+        }
+
+        // Perform updates.
+        r.overrideConfig = overrideConfig;
+        final ViewRootImpl viewRoot = r.activity.mDecor != null
+            ? r.activity.mDecor.getViewRootImpl() : null;
+
+        if (movedToDifferentDisplay) {
+            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity moved to display, activity:"
+                    + r.activityInfo.name + ", displayId=" + displayId
+                    + ", config=" + overrideConfig);
+
+            final Configuration reportedConfig = performConfigurationChangedForActivity(r,
+                    mCompatConfiguration, displayId, true /* movedToDifferentDisplay */);
+            if (viewRoot != null) {
+                viewRoot.onMovedToDisplay(displayId, reportedConfig);
+            }
+        } else {
+            if (DEBUG_CONFIGURATION) Slog.v(TAG, "Handle activity config changed: "
+                    + r.activityInfo.name + ", config=" + overrideConfig);
+            performConfigurationChangedForActivity(r, mCompatConfiguration);
+        }
+        // Notify the ViewRootImpl instance about configuration changes. It may have initiated this
+        // update to make sure that resources are updated before updating itself.
+        if (viewRoot != null) {
+            viewRoot.updateConfiguration(displayId);
+        }
+        mSomeActivitiesChanged = true;
+    }
+
+    final void handleProfilerControl(boolean start, ProfilerInfo profilerInfo, int profileType) {
+        if (start) {
+            try {
+                switch (profileType) {
+                    default:
+                        mProfiler.setProfiler(profilerInfo);
+                        mProfiler.startProfiling();
+                        break;
+                }
+            } catch (RuntimeException e) {
+                Slog.w(TAG, "Profiling failed on path " + profilerInfo.profileFile
+                        + " -- can the process access this path?");
+            } finally {
+                profilerInfo.closeFd();
+            }
+        } else {
+            switch (profileType) {
+                default:
+                    mProfiler.stopProfiling();
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Public entrypoint to stop profiling. This is required to end profiling when the app crashes,
+     * so that profiler data won't be lost.
+     *
+     * @hide
+     */
+    public void stopProfiling() {
+        if (mProfiler != null) {
+            mProfiler.stopProfiling();
+        }
+    }
+
+    static void handleDumpHeap(DumpHeapData dhd) {
+        if (dhd.runGc) {
+            System.gc();
+            System.runFinalization();
+            System.gc();
+        }
+        try (ParcelFileDescriptor fd = dhd.fd) {
+            if (dhd.managed) {
+                Debug.dumpHprofData(dhd.path, fd.getFileDescriptor());
+            } else if (dhd.mallocInfo) {
+                Debug.dumpNativeMallocInfo(fd.getFileDescriptor());
+            } else {
+                Debug.dumpNativeHeap(fd.getFileDescriptor());
+            }
+        } catch (IOException e) {
+            if (dhd.managed) {
+                Slog.w(TAG, "Managed heap dump failed on path " + dhd.path
+                        + " -- can the process access this path?", e);
+            } else {
+                Slog.w(TAG, "Failed to dump heap", e);
+            }
+        } catch (RuntimeException e) {
+            // This should no longer happening now that we're copying the file descriptor.
+            Slog.wtf(TAG, "Heap dumper threw a runtime exception", e);
+        }
+        try {
+            ActivityManager.getService().dumpHeapFinished(dhd.path);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        if (dhd.finishCallback != null) {
+            dhd.finishCallback.sendResult(null);
+        }
+    }
+
+    final void handleDispatchPackageBroadcast(int cmd, String[] packages) {
+        boolean hasPkgInfo = false;
+        switch (cmd) {
+            case ApplicationThreadConstants.PACKAGE_REMOVED:
+            case ApplicationThreadConstants.PACKAGE_REMOVED_DONT_KILL:
+            {
+                final boolean killApp = cmd == ApplicationThreadConstants.PACKAGE_REMOVED;
+                if (packages == null) {
+                    break;
+                }
+                synchronized (mResourcesManager) {
+                    for (int i = packages.length - 1; i >= 0; i--) {
+                        if (!hasPkgInfo) {
+                            WeakReference<LoadedApk> ref = mPackages.get(packages[i]);
+                            if (ref != null && ref.get() != null) {
+                                hasPkgInfo = true;
+                            } else {
+                                ref = mResourcePackages.get(packages[i]);
+                                if (ref != null && ref.get() != null) {
+                                    hasPkgInfo = true;
+                                }
+                            }
+                        }
+                        if (killApp) {
+                            mPackages.remove(packages[i]);
+                            mResourcePackages.remove(packages[i]);
+                        }
+                    }
+                }
+                break;
+            }
+            case ApplicationThreadConstants.PACKAGE_REPLACED:
+            {
+                if (packages == null) {
+                    break;
+                }
+
+                List<String> packagesHandled = new ArrayList<>();
+
+                synchronized (mResourcesManager) {
+                    for (int i = packages.length - 1; i >= 0; i--) {
+                        String packageName = packages[i];
+                        WeakReference<LoadedApk> ref = mPackages.get(packageName);
+                        LoadedApk pkgInfo = ref != null ? ref.get() : null;
+                        if (pkgInfo != null) {
+                            hasPkgInfo = true;
+                        } else {
+                            ref = mResourcePackages.get(packageName);
+                            pkgInfo = ref != null ? ref.get() : null;
+                            if (pkgInfo != null) {
+                                hasPkgInfo = true;
+                            }
+                        }
+                        // If the package is being replaced, yet it still has a valid
+                        // LoadedApk object, the package was updated with _DONT_KILL.
+                        // Adjust it's internal references to the application info and
+                        // resources.
+                        if (pkgInfo != null) {
+                            packagesHandled.add(packageName);
+                            try {
+                                final ApplicationInfo aInfo =
+                                        sPackageManager.getApplicationInfo(
+                                                packageName,
+                                                PackageManager.GET_SHARED_LIBRARY_FILES,
+                                                UserHandle.myUserId());
+
+                                if (mActivities.size() > 0) {
+                                    for (ActivityClientRecord ar : mActivities.values()) {
+                                        if (ar.activityInfo.applicationInfo.packageName
+                                                .equals(packageName)) {
+                                            ar.activityInfo.applicationInfo = aInfo;
+                                            ar.packageInfo = pkgInfo;
+                                        }
+                                    }
+                                }
+
+                                final String[] oldResDirs = { pkgInfo.getResDir() };
+
+                                final ArrayList<String> oldPaths = new ArrayList<>();
+                                LoadedApk.makePaths(this, pkgInfo.getApplicationInfo(), oldPaths);
+                                pkgInfo.updateApplicationInfo(aInfo, oldPaths);
+
+                                synchronized (mResourcesManager) {
+                                    // Update affected Resources objects to use new ResourcesImpl
+                                    mResourcesManager.applyNewResourceDirsLocked(aInfo, oldResDirs);
+                                }
+                            } catch (RemoteException e) {
+                            }
+                        }
+                    }
+                }
+
+                try {
+                    getPackageManager().notifyPackagesReplacedReceived(
+                            packagesHandled.toArray(new String[0]));
+                } catch (RemoteException ignored) {
+                }
+
+                break;
+            }
+        }
+        ApplicationPackageManager.handlePackageBroadcast(cmd, packages, hasPkgInfo);
+    }
+
+    final void handleLowMemory() {
+        ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);
+
+        final int N = callbacks.size();
+        for (int i=0; i<N; i++) {
+            callbacks.get(i).onLowMemory();
+        }
+
+        // Ask SQLite to free up as much memory as it can, mostly from its page caches.
+        if (Process.myUid() != Process.SYSTEM_UID) {
+            int sqliteReleased = SQLiteDatabase.releaseMemory();
+            EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased);
+        }
+
+        // Ask graphics to free up as much as possible (font/image caches)
+        Canvas.freeCaches();
+
+        // Ask text layout engine to free also as much as possible
+        Canvas.freeTextLayoutCaches();
+
+        BinderInternal.forceGc("mem");
+    }
+
+    private void handleTrimMemory(int level) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "trimMemory");
+        if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);
+
+        ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);
+
+        final int N = callbacks.size();
+        for (int i = 0; i < N; i++) {
+            callbacks.get(i).onTrimMemory(level);
+        }
+
+        WindowManagerGlobal.getInstance().trimMemory(level);
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        if (SystemProperties.getInt("debug.am.run_gc_trim_level", Integer.MAX_VALUE) <= level) {
+            unscheduleGcIdler();
+            doGcIfNeeded("tm");
+        }
+        if (SystemProperties.getInt("debug.am.run_mallopt_trim_level", Integer.MAX_VALUE)
+                <= level) {
+            unschedulePurgeIdler();
+            purgePendingResources();
+        }
+    }
+
+    private void setupGraphicsSupport(Context context) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setupGraphicsSupport");
+
+        // The system package doesn't have real data directories, so don't set up cache paths.
+        if (!"android".equals(context.getPackageName())) {
+            // This cache location probably points at credential-encrypted
+            // storage which may not be accessible yet; assign it anyway instead
+            // of pointing at device-encrypted storage.
+            final File cacheDir = context.getCacheDir();
+            if (cacheDir != null) {
+                // Provide a usable directory for temporary files
+                System.setProperty("java.io.tmpdir", cacheDir.getAbsolutePath());
+            } else {
+                Log.v(TAG, "Unable to initialize \"java.io.tmpdir\" property "
+                        + "due to missing cache directory");
+            }
+
+            // Setup a location to store generated/compiled graphics code.
+            final Context deviceContext = context.createDeviceProtectedStorageContext();
+            final File codeCacheDir = deviceContext.getCodeCacheDir();
+            if (codeCacheDir != null) {
+                try {
+                    int uid = Process.myUid();
+                    String[] packages = getPackageManager().getPackagesForUid(uid);
+                    if (packages != null) {
+                        HardwareRenderer.setupDiskCache(codeCacheDir);
+                        RenderScriptCacheDir.setupDiskCache(codeCacheDir);
+                    }
+                } catch (RemoteException e) {
+                    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                    throw e.rethrowFromSystemServer();
+                }
+            } else {
+                Log.w(TAG, "Unable to use shader/script cache: missing code-cache directory");
+            }
+        }
+
+        GraphicsEnvironment.getInstance().setup(context, mCoreSettings);
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    private void updateDefaultDensity() {
+        final int densityDpi = mCurDefaultDisplayDpi;
+        if (!mDensityCompatMode
+                && densityDpi != Configuration.DENSITY_DPI_UNDEFINED
+                && densityDpi != DisplayMetrics.DENSITY_DEVICE) {
+            DisplayMetrics.DENSITY_DEVICE = densityDpi;
+            Bitmap.setDefaultDensity(densityDpi);
+        }
+    }
+
+    /**
+     * Returns the correct library directory for the current ABI.
+     * <p>
+     * If we're dealing with a multi-arch application that has both 32 and 64 bit shared
+     * libraries, we might need to choose the secondary depending on what the current
+     * runtime's instruction set is.
+     */
+    private String getInstrumentationLibrary(ApplicationInfo appInfo, InstrumentationInfo insInfo) {
+        if (appInfo.primaryCpuAbi != null && appInfo.secondaryCpuAbi != null
+                && appInfo.secondaryCpuAbi.equals(insInfo.secondaryCpuAbi)) {
+            // Get the instruction set supported by the secondary ABI. In the presence
+            // of a native bridge this might be different than the one secondary ABI used.
+            String secondaryIsa =
+                    VMRuntime.getInstructionSet(appInfo.secondaryCpuAbi);
+            final String secondaryDexCodeIsa =
+                    SystemProperties.get("ro.dalvik.vm.isa." + secondaryIsa);
+            secondaryIsa = secondaryDexCodeIsa.isEmpty() ? secondaryIsa : secondaryDexCodeIsa;
+
+            final String runtimeIsa = VMRuntime.getRuntime().vmInstructionSet();
+            if (runtimeIsa.equals(secondaryIsa)) {
+                return insInfo.secondaryNativeLibraryDir;
+            }
+        }
+        return insInfo.nativeLibraryDir;
+    }
+
+    /**
+     * The LocaleList set for the app's resources may have been shuffled so that the preferred
+     * Locale is at position 0. We must find the index of this preferred Locale in the
+     * original LocaleList.
+     */
+    private void updateLocaleListFromAppContext(Context context, LocaleList newLocaleList) {
+        final Locale bestLocale = context.getResources().getConfiguration().getLocales().get(0);
+        final int newLocaleListSize = newLocaleList.size();
+        for (int i = 0; i < newLocaleListSize; i++) {
+            if (bestLocale.equals(newLocaleList.get(i))) {
+                LocaleList.setDefault(newLocaleList, i);
+                return;
+            }
+        }
+
+        // The app may have overridden the LocaleList with its own Locale
+        // (not present in the available list). Push the chosen Locale
+        // to the front of the list.
+        LocaleList.setDefault(new LocaleList(bestLocale, newLocaleList));
+    }
+
+    @UnsupportedAppUsage
+    private void handleBindApplication(AppBindData data) {
+        // Register the UI Thread as a sensitive thread to the runtime.
+        VMRuntime.registerSensitiveThread();
+        // In the case the stack depth property exists, pass it down to the runtime.
+        String property = SystemProperties.get("debug.allocTracker.stackDepth");
+        if (property.length() != 0) {
+            VMDebug.setAllocTrackerStackDepth(Integer.parseInt(property));
+        }
+        if (data.trackAllocation) {
+            DdmVmInternal.enableRecentAllocations(true);
+        }
+
+        // Note when this process has started.
+        Process.setStartTimes(SystemClock.elapsedRealtime(), SystemClock.uptimeMillis());
+
+        mBoundApplication = data;
+        mConfiguration = new Configuration(data.config);
+        mCompatConfiguration = new Configuration(data.config);
+
+        mProfiler = new Profiler();
+        String agent = null;
+        if (data.initProfilerInfo != null) {
+            mProfiler.profileFile = data.initProfilerInfo.profileFile;
+            mProfiler.profileFd = data.initProfilerInfo.profileFd;
+            mProfiler.samplingInterval = data.initProfilerInfo.samplingInterval;
+            mProfiler.autoStopProfiler = data.initProfilerInfo.autoStopProfiler;
+            mProfiler.streamingOutput = data.initProfilerInfo.streamingOutput;
+            if (data.initProfilerInfo.attachAgentDuringBind) {
+                agent = data.initProfilerInfo.agent;
+            }
+        }
+
+        // send up app name; do this *before* waiting for debugger
+        Process.setArgV0(data.processName);
+        android.ddm.DdmHandleAppName.setAppName(data.processName,
+                                                UserHandle.myUserId());
+        VMRuntime.setProcessPackageName(data.appInfo.packageName);
+
+        // Pass data directory path to ART. This is used for caching information and
+        // should be set before any application code is loaded.
+        VMRuntime.setProcessDataDirectory(data.appInfo.dataDir);
+
+        if (mProfiler.profileFd != null) {
+            mProfiler.startProfiling();
+        }
+
+        // If the app is Honeycomb MR1 or earlier, switch its AsyncTask
+        // implementation to use the pool executor.  Normally, we use the
+        // serialized executor as the default. This has to happen in the
+        // main thread so the main looper is set right.
+        if (data.appInfo.targetSdkVersion <= android.os.Build.VERSION_CODES.HONEYCOMB_MR1) {
+            AsyncTask.setDefaultExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+        }
+
+        // Let the util.*Array classes maintain "undefined" for apps targeting Pie or earlier.
+        UtilConfig.setThrowExceptionForUpperArrayOutOfBounds(
+                data.appInfo.targetSdkVersion >= Build.VERSION_CODES.Q);
+
+        Message.updateCheckRecycle(data.appInfo.targetSdkVersion);
+
+        // Prior to P, internal calls to decode Bitmaps used BitmapFactory,
+        // which may scale up to account for density. In P, we switched to
+        // ImageDecoder, which skips the upscale to save memory. ImageDecoder
+        // needs to still scale up in older apps, in case they rely on the
+        // size of the Bitmap without considering its density.
+        ImageDecoder.sApiLevel = data.appInfo.targetSdkVersion;
+
+        /*
+         * Before spawning a new process, reset the time zone to be the system time zone.
+         * This needs to be done because the system time zone could have changed after the
+         * the spawning of this process. Without doing this this process would have the incorrect
+         * system time zone.
+         */
+        TimeZone.setDefault(null);
+
+        /*
+         * Set the LocaleList. This may change once we create the App Context.
+         */
+        LocaleList.setDefault(data.config.getLocales());
+
+        synchronized (mResourcesManager) {
+            /*
+             * Update the system configuration since its preloaded and might not
+             * reflect configuration changes. The configuration object passed
+             * in AppBindData can be safely assumed to be up to date
+             */
+            mResourcesManager.applyConfigurationToResourcesLocked(data.config, data.compatInfo);
+            mCurDefaultDisplayDpi = data.config.densityDpi;
+
+            // This calls mResourcesManager so keep it within the synchronized block.
+            applyCompatConfiguration(mCurDefaultDisplayDpi);
+        }
+
+        data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
+
+        if (agent != null) {
+            handleAttachAgent(agent, data.info);
+        }
+
+        /**
+         * Switch this process to density compatibility mode if needed.
+         */
+        if ((data.appInfo.flags&ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
+                == 0) {
+            mDensityCompatMode = true;
+            Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT);
+        }
+        updateDefaultDensity();
+
+        final String use24HourSetting = mCoreSettings.getString(Settings.System.TIME_12_24);
+        Boolean is24Hr = null;
+        if (use24HourSetting != null) {
+            is24Hr = "24".equals(use24HourSetting) ? Boolean.TRUE : Boolean.FALSE;
+        }
+        // null : use locale default for 12/24 hour formatting,
+        // false : use 12 hour format,
+        // true : use 24 hour format.
+        DateFormat.set24HourTimePref(is24Hr);
+
+        updateDebugViewAttributeState();
+
+        StrictMode.initThreadDefaults(data.appInfo);
+        StrictMode.initVmDefaults(data.appInfo);
+
+        if (data.debugMode != ApplicationThreadConstants.DEBUG_OFF) {
+            // XXX should have option to change the port.
+            Debug.changeDebugPort(8100);
+            if (data.debugMode == ApplicationThreadConstants.DEBUG_WAIT) {
+                Slog.w(TAG, "Application " + data.info.getPackageName()
+                      + " is waiting for the debugger on port 8100...");
+
+                IActivityManager mgr = ActivityManager.getService();
+                try {
+                    mgr.showWaitingForDebugger(mAppThread, true);
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+
+                Debug.waitForDebugger();
+
+                try {
+                    mgr.showWaitingForDebugger(mAppThread, false);
+                } catch (RemoteException ex) {
+                    throw ex.rethrowFromSystemServer();
+                }
+
+            } else {
+                Slog.w(TAG, "Application " + data.info.getPackageName()
+                      + " can be debugged on port 8100...");
+            }
+        }
+
+        // Allow binder tracing, and application-generated systrace messages if we're profileable.
+        boolean isAppProfileable = data.appInfo.isProfileableByShell();
+        Trace.setAppTracingAllowed(isAppProfileable);
+        if (isAppProfileable && data.enableBinderTracking) {
+            Binder.enableTracing();
+        }
+
+        // Initialize heap profiling.
+        if (isAppProfileable || Build.IS_DEBUGGABLE) {
+            nInitZygoteChildHeapProfiling();
+        }
+
+        // Allow renderer debugging features if we're debuggable.
+        boolean isAppDebuggable = (data.appInfo.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
+        HardwareRenderer.setDebuggingEnabled(isAppDebuggable || Build.IS_DEBUGGABLE);
+        HardwareRenderer.setPackageName(data.appInfo.packageName);
+
+        /**
+         * Initialize the default http proxy in this process for the reasons we set the time zone.
+         */
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "Setup proxies");
+        final IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE);
+        if (b != null) {
+            // In pre-boot mode (doing initial launch to collect password), not
+            // all system is up.  This includes the connectivity service, so don't
+            // crash if we can't get it.
+            final IConnectivityManager service = IConnectivityManager.Stub.asInterface(b);
+            try {
+                Proxy.setHttpProxySystemProperty(service.getProxyForNetwork(null));
+            } catch (RemoteException e) {
+                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+                throw e.rethrowFromSystemServer();
+            }
+        }
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        // Instrumentation info affects the class loader, so load it before
+        // setting up the app context.
+        final InstrumentationInfo ii;
+        if (data.instrumentationName != null) {
+            try {
+                ii = new ApplicationPackageManager(null, getPackageManager())
+                        .getInstrumentationInfo(data.instrumentationName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                throw new RuntimeException(
+                        "Unable to find instrumentation info for: " + data.instrumentationName);
+            }
+
+            // Warn of potential ABI mismatches.
+            if (!Objects.equals(data.appInfo.primaryCpuAbi, ii.primaryCpuAbi)
+                    || !Objects.equals(data.appInfo.secondaryCpuAbi, ii.secondaryCpuAbi)) {
+                Slog.w(TAG, "Package uses different ABI(s) than its instrumentation: "
+                        + "package[" + data.appInfo.packageName + "]: "
+                        + data.appInfo.primaryCpuAbi + ", " + data.appInfo.secondaryCpuAbi
+                        + " instrumentation[" + ii.packageName + "]: "
+                        + ii.primaryCpuAbi + ", " + ii.secondaryCpuAbi);
+            }
+
+            mInstrumentationPackageName = ii.packageName;
+            mInstrumentationAppDir = ii.sourceDir;
+            mInstrumentationSplitAppDirs = ii.splitSourceDirs;
+            mInstrumentationLibDir = getInstrumentationLibrary(data.appInfo, ii);
+            mInstrumentedAppDir = data.info.getAppDir();
+            mInstrumentedSplitAppDirs = data.info.getSplitAppDirs();
+            mInstrumentedLibDir = data.info.getLibDir();
+        } else {
+            ii = null;
+        }
+
+        final ContextImpl appContext = ContextImpl.createAppContext(this, data.info);
+        updateLocaleListFromAppContext(appContext,
+                mResourcesManager.getConfiguration().getLocales());
+
+        if (!Process.isIsolated()) {
+            final int oldMask = StrictMode.allowThreadDiskWritesMask();
+            try {
+                setupGraphicsSupport(appContext);
+            } finally {
+                StrictMode.setThreadPolicyMask(oldMask);
+            }
+        } else {
+            HardwareRenderer.setIsolatedProcess(true);
+        }
+
+        // Install the Network Security Config Provider. This must happen before the application
+        // code is loaded to prevent issues with instances of TLS objects being created before
+        // the provider is installed.
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "NetworkSecurityConfigProvider.install");
+        NetworkSecurityConfigProvider.install(appContext);
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+        // Continue loading instrumentation.
+        if (ii != null) {
+            ApplicationInfo instrApp;
+            try {
+                instrApp = getPackageManager().getApplicationInfo(ii.packageName, 0,
+                        UserHandle.myUserId());
+            } catch (RemoteException e) {
+                instrApp = null;
+            }
+            if (instrApp == null) {
+                instrApp = new ApplicationInfo();
+            }
+            ii.copyTo(instrApp);
+            instrApp.initForUser(UserHandle.myUserId());
+            final LoadedApk pi = getPackageInfo(instrApp, data.compatInfo,
+                    appContext.getClassLoader(), false, true, false);
+
+            // The test context's op package name == the target app's op package name, because
+            // the app ops manager checks the op package name against the real calling UID,
+            // which is what the target package name is associated with.
+            final ContextImpl instrContext = ContextImpl.createAppContext(this, pi,
+                    appContext.getOpPackageName());
+
+            try {
+                final ClassLoader cl = instrContext.getClassLoader();
+                mInstrumentation = (Instrumentation)
+                    cl.loadClass(data.instrumentationName.getClassName()).newInstance();
+            } catch (Exception e) {
+                throw new RuntimeException(
+                    "Unable to instantiate instrumentation "
+                    + data.instrumentationName + ": " + e.toString(), e);
+            }
+
+            final ComponentName component = new ComponentName(ii.packageName, ii.name);
+            mInstrumentation.init(this, instrContext, appContext, component,
+                    data.instrumentationWatcher, data.instrumentationUiAutomationConnection);
+
+            if (mProfiler.profileFile != null && !ii.handleProfiling
+                    && mProfiler.profileFd == null) {
+                mProfiler.handlingProfiling = true;
+                final File file = new File(mProfiler.profileFile);
+                file.getParentFile().mkdirs();
+                Debug.startMethodTracing(file.toString(), 8 * 1024 * 1024);
+            }
+        } else {
+            mInstrumentation = new Instrumentation();
+            mInstrumentation.basicInit(this);
+        }
+
+        if ((data.appInfo.flags&ApplicationInfo.FLAG_LARGE_HEAP) != 0) {
+            dalvik.system.VMRuntime.getRuntime().clearGrowthLimit();
+        } else {
+            // Small heap, clamp to the current growth limit and let the heap release
+            // pages after the growth limit to the non growth limit capacity. b/18387825
+            dalvik.system.VMRuntime.getRuntime().clampGrowthLimit();
+        }
+
+        // Allow disk access during application and provider setup. This could
+        // block processing ordered broadcasts, but later processing would
+        // probably end up doing the same disk access.
+        Application app;
+        final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
+        final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
+        try {
+            // If the app is being launched for full backup or restore, bring it up in
+            // a restricted environment with the base application class.
+            app = data.info.makeApplication(data.restrictedBackupMode, null);
+
+            // Propagate autofill compat state
+            app.setAutofillOptions(data.autofillOptions);
+
+            // Propagate Content Capture options
+            app.setContentCaptureOptions(data.contentCaptureOptions);
+
+            mInitialApplication = app;
+
+            // don't bring up providers in restricted mode; they may depend on the
+            // app's custom Application class
+            if (!data.restrictedBackupMode) {
+                if (!ArrayUtils.isEmpty(data.providers)) {
+                    installContentProviders(app, data.providers);
+                }
+            }
+
+            // Do this after providers, since instrumentation tests generally start their
+            // test thread at this point, and we don't want that racing.
+            try {
+                mInstrumentation.onCreate(data.instrumentationArgs);
+            }
+            catch (Exception e) {
+                throw new RuntimeException(
+                    "Exception thrown in onCreate() of "
+                    + data.instrumentationName + ": " + e.toString(), e);
+            }
+            try {
+                mInstrumentation.callApplicationOnCreate(app);
+            } catch (Exception e) {
+                if (!mInstrumentation.onException(app, e)) {
+                    throw new RuntimeException(
+                      "Unable to create application " + app.getClass().getName()
+                      + ": " + e.toString(), e);
+                }
+            }
+        } finally {
+            // If the app targets < O-MR1, or doesn't change the thread policy
+            // during startup, clobber the policy to maintain behavior of b/36951662
+            if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
+                    || StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
+                StrictMode.setThreadPolicy(savedPolicy);
+            }
+        }
+
+        // Preload fonts resources
+        FontsContract.setApplicationContextForResources(appContext);
+        if (!Process.isIsolated()) {
+            try {
+                final ApplicationInfo info =
+                        getPackageManager().getApplicationInfo(
+                                data.appInfo.packageName,
+                                PackageManager.GET_META_DATA /*flags*/,
+                                UserHandle.myUserId());
+                if (info.metaData != null) {
+                    final int preloadedFontsResource = info.metaData.getInt(
+                            ApplicationInfo.METADATA_PRELOADED_FONTS, 0);
+                    if (preloadedFontsResource != 0) {
+                        data.info.getResources().preloadFonts(preloadedFontsResource);
+                    }
+                }
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /*package*/ final void finishInstrumentation(int resultCode, Bundle results) {
+        IActivityManager am = ActivityManager.getService();
+        if (mProfiler.profileFile != null && mProfiler.handlingProfiling
+                && mProfiler.profileFd == null) {
+            Debug.stopMethodTracing();
+        }
+        //Slog.i(TAG, "am: " + ActivityManager.getService()
+        //      + ", app thr: " + mAppThread);
+        try {
+            am.finishInstrumentation(mAppThread, resultCode, results);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    @UnsupportedAppUsage
+    private void installContentProviders(
+            Context context, List<ProviderInfo> providers) {
+        final ArrayList<ContentProviderHolder> results = new ArrayList<>();
+
+        for (ProviderInfo cpi : providers) {
+            if (DEBUG_PROVIDER) {
+                StringBuilder buf = new StringBuilder(128);
+                buf.append("Pub ");
+                buf.append(cpi.authority);
+                buf.append(": ");
+                buf.append(cpi.name);
+                Log.i(TAG, buf.toString());
+            }
+            ContentProviderHolder cph = installProvider(context, null, cpi,
+                    false /*noisy*/, true /*noReleaseNeeded*/, true /*stable*/);
+            if (cph != null) {
+                cph.noReleaseNeeded = true;
+                results.add(cph);
+            }
+        }
+
+        try {
+            ActivityManager.getService().publishContentProviders(
+                getApplicationThread(), results);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    @UnsupportedAppUsage
+    public final IContentProvider acquireProvider(
+            Context c, String auth, int userId, boolean stable) {
+        final IContentProvider provider = acquireExistingProvider(c, auth, userId, stable);
+        if (provider != null) {
+            return provider;
+        }
+
+        // There is a possible race here.  Another thread may try to acquire
+        // the same provider at the same time.  When this happens, we want to ensure
+        // that the first one wins.
+        // Note that we cannot hold the lock while acquiring and installing the
+        // provider since it might take a long time to run and it could also potentially
+        // be re-entrant in the case where the provider is in the same process.
+        ContentProviderHolder holder = null;
+        try {
+            synchronized (getGetProviderLock(auth, userId)) {
+                holder = ActivityManager.getService().getContentProvider(
+                        getApplicationThread(), c.getOpPackageName(), auth, userId, stable);
+            }
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+        if (holder == null) {
+            Slog.e(TAG, "Failed to find provider info for " + auth);
+            return null;
+        }
+
+        // Install provider will increment the reference count for us, and break
+        // any ties in the race.
+        holder = installProvider(c, holder, holder.info,
+                true /*noisy*/, holder.noReleaseNeeded, stable);
+        return holder.provider;
+    }
+
+    private Object getGetProviderLock(String auth, int userId) {
+        final ProviderKey key = new ProviderKey(auth, userId);
+        synchronized (mGetProviderLocks) {
+            Object lock = mGetProviderLocks.get(key);
+            if (lock == null) {
+                lock = key;
+                mGetProviderLocks.put(key, lock);
+            }
+            return lock;
+        }
+    }
+
+    private final void incProviderRefLocked(ProviderRefCount prc, boolean stable) {
+        if (stable) {
+            prc.stableCount += 1;
+            if (prc.stableCount == 1) {
+                // We are acquiring a new stable reference on the provider.
+                int unstableDelta;
+                if (prc.removePending) {
+                    // We have a pending remove operation, which is holding the
+                    // last unstable reference.  At this point we are converting
+                    // that unstable reference to our new stable reference.
+                    unstableDelta = -1;
+                    // Cancel the removal of the provider.
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "incProviderRef: stable "
+                                + "snatched provider from the jaws of death");
+                    }
+                    prc.removePending = false;
+                    // There is a race! It fails to remove the message, which
+                    // will be handled in completeRemoveProvider().
+                    mH.removeMessages(H.REMOVE_PROVIDER, prc);
+                } else {
+                    unstableDelta = 0;
+                }
+                try {
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "incProviderRef Now stable - "
+                                + prc.holder.info.name + ": unstableDelta="
+                                + unstableDelta);
+                    }
+                    ActivityManager.getService().refContentProvider(
+                            prc.holder.connection, 1, unstableDelta);
+                } catch (RemoteException e) {
+                    //do nothing content provider object is dead any way
+                }
+            }
+        } else {
+            prc.unstableCount += 1;
+            if (prc.unstableCount == 1) {
+                // We are acquiring a new unstable reference on the provider.
+                if (prc.removePending) {
+                    // Oh look, we actually have a remove pending for the
+                    // provider, which is still holding the last unstable
+                    // reference.  We just need to cancel that to take new
+                    // ownership of the reference.
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "incProviderRef: unstable "
+                                + "snatched provider from the jaws of death");
+                    }
+                    prc.removePending = false;
+                    mH.removeMessages(H.REMOVE_PROVIDER, prc);
+                } else {
+                    // First unstable ref, increment our count in the
+                    // activity manager.
+                    try {
+                        if (DEBUG_PROVIDER) {
+                            Slog.v(TAG, "incProviderRef: Now unstable - "
+                                    + prc.holder.info.name);
+                        }
+                        ActivityManager.getService().refContentProvider(
+                                prc.holder.connection, 0, 1);
+                    } catch (RemoteException e) {
+                        //do nothing content provider object is dead any way
+                    }
+                }
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    public final IContentProvider acquireExistingProvider(
+            Context c, String auth, int userId, boolean stable) {
+        synchronized (mProviderMap) {
+            final ProviderKey key = new ProviderKey(auth, userId);
+            final ProviderClientRecord pr = mProviderMap.get(key);
+            if (pr == null) {
+                return null;
+            }
+
+            IContentProvider provider = pr.mProvider;
+            IBinder jBinder = provider.asBinder();
+            if (!jBinder.isBinderAlive()) {
+                // The hosting process of the provider has died; we can't
+                // use this one.
+                Log.i(TAG, "Acquiring provider " + auth + " for user " + userId
+                        + ": existing object's process dead");
+                handleUnstableProviderDiedLocked(jBinder, true);
+                return null;
+            }
+
+            // Only increment the ref count if we have one.  If we don't then the
+            // provider is not reference counted and never needs to be released.
+            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
+            if (prc != null) {
+                incProviderRefLocked(prc, stable);
+            }
+            return provider;
+        }
+    }
+
+    @UnsupportedAppUsage
+    public final boolean releaseProvider(IContentProvider provider, boolean stable) {
+        if (provider == null) {
+            return false;
+        }
+
+        IBinder jBinder = provider.asBinder();
+        synchronized (mProviderMap) {
+            ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
+            if (prc == null) {
+                // The provider has no ref count, no release is needed.
+                return false;
+            }
+
+            boolean lastRef = false;
+            if (stable) {
+                if (prc.stableCount == 0) {
+                    if (DEBUG_PROVIDER) Slog.v(TAG,
+                            "releaseProvider: stable ref count already 0, how?");
+                    return false;
+                }
+                prc.stableCount -= 1;
+                if (prc.stableCount == 0) {
+                    // What we do at this point depends on whether there are
+                    // any unstable refs left: if there are, we just tell the
+                    // activity manager to decrement its stable count; if there
+                    // aren't, we need to enqueue this provider to be removed,
+                    // and convert to holding a single unstable ref while
+                    // doing so.
+                    lastRef = prc.unstableCount == 0;
+                    try {
+                        if (DEBUG_PROVIDER) {
+                            Slog.v(TAG, "releaseProvider: No longer stable w/lastRef="
+                                    + lastRef + " - " + prc.holder.info.name);
+                        }
+                        ActivityManager.getService().refContentProvider(
+                                prc.holder.connection, -1, lastRef ? 1 : 0);
+                    } catch (RemoteException e) {
+                        //do nothing content provider object is dead any way
+                    }
+                }
+            } else {
+                if (prc.unstableCount == 0) {
+                    if (DEBUG_PROVIDER) Slog.v(TAG,
+                            "releaseProvider: unstable ref count already 0, how?");
+                    return false;
+                }
+                prc.unstableCount -= 1;
+                if (prc.unstableCount == 0) {
+                    // If this is the last reference, we need to enqueue
+                    // this provider to be removed instead of telling the
+                    // activity manager to remove it at this point.
+                    lastRef = prc.stableCount == 0;
+                    if (!lastRef) {
+                        try {
+                            if (DEBUG_PROVIDER) {
+                                Slog.v(TAG, "releaseProvider: No longer unstable - "
+                                        + prc.holder.info.name);
+                            }
+                            ActivityManager.getService().refContentProvider(
+                                    prc.holder.connection, 0, -1);
+                        } catch (RemoteException e) {
+                            //do nothing content provider object is dead any way
+                        }
+                    }
+                }
+            }
+
+            if (lastRef) {
+                if (!prc.removePending) {
+                    // Schedule the actual remove asynchronously, since we don't know the context
+                    // this will be called in.
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "releaseProvider: Enqueueing pending removal - "
+                                + prc.holder.info.name);
+                    }
+                    prc.removePending = true;
+                    Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, prc);
+                    mH.sendMessageDelayed(msg, CONTENT_PROVIDER_RETAIN_TIME);
+                } else {
+                    Slog.w(TAG, "Duplicate remove pending of provider " + prc.holder.info.name);
+                }
+            }
+            return true;
+        }
+    }
+
+    final void completeRemoveProvider(ProviderRefCount prc) {
+        synchronized (mProviderMap) {
+            if (!prc.removePending) {
+                // There was a race!  Some other client managed to acquire
+                // the provider before the removal was completed.
+                // Abort the removal.  We will do it later.
+                if (DEBUG_PROVIDER) Slog.v(TAG, "completeRemoveProvider: lost the race, "
+                        + "provider still in use");
+                return;
+            }
+
+            // More complicated race!! Some client managed to acquire the
+            // provider and release it before the removal was completed.
+            // Continue the removal, and abort the next remove message.
+            prc.removePending = false;
+
+            final IBinder jBinder = prc.holder.provider.asBinder();
+            ProviderRefCount existingPrc = mProviderRefCountMap.get(jBinder);
+            if (existingPrc == prc) {
+                mProviderRefCountMap.remove(jBinder);
+            }
+
+            for (int i=mProviderMap.size()-1; i>=0; i--) {
+                ProviderClientRecord pr = mProviderMap.valueAt(i);
+                IBinder myBinder = pr.mProvider.asBinder();
+                if (myBinder == jBinder) {
+                    mProviderMap.removeAt(i);
+                }
+            }
+        }
+
+        try {
+            if (DEBUG_PROVIDER) {
+                Slog.v(TAG, "removeProvider: Invoking ActivityManagerService."
+                        + "removeContentProvider(" + prc.holder.info.name + ")");
+            }
+            ActivityManager.getService().removeContentProvider(
+                    prc.holder.connection, false);
+        } catch (RemoteException e) {
+            //do nothing content provider object is dead any way
+        }
+    }
+
+    @UnsupportedAppUsage
+    final void handleUnstableProviderDied(IBinder provider, boolean fromClient) {
+        synchronized (mProviderMap) {
+            handleUnstableProviderDiedLocked(provider, fromClient);
+        }
+    }
+
+    final void handleUnstableProviderDiedLocked(IBinder provider, boolean fromClient) {
+        ProviderRefCount prc = mProviderRefCountMap.get(provider);
+        if (prc != null) {
+            if (DEBUG_PROVIDER) Slog.v(TAG, "Cleaning up dead provider "
+                    + provider + " " + prc.holder.info.name);
+            mProviderRefCountMap.remove(provider);
+            for (int i=mProviderMap.size()-1; i>=0; i--) {
+                ProviderClientRecord pr = mProviderMap.valueAt(i);
+                if (pr != null && pr.mProvider.asBinder() == provider) {
+                    Slog.i(TAG, "Removing dead content provider:" + pr.mProvider.toString());
+                    mProviderMap.removeAt(i);
+                }
+            }
+
+            if (fromClient) {
+                // We found out about this due to execution in our client
+                // code.  Tell the activity manager about it now, to ensure
+                // that the next time we go to do anything with the provider
+                // it knows it is dead (so we don't race with its death
+                // notification).
+                try {
+                    ActivityManager.getService().unstableProviderDied(
+                            prc.holder.connection);
+                } catch (RemoteException e) {
+                    //do nothing content provider object is dead any way
+                }
+            }
+        }
+    }
+
+    final void appNotRespondingViaProvider(IBinder provider) {
+        synchronized (mProviderMap) {
+            ProviderRefCount prc = mProviderRefCountMap.get(provider);
+            if (prc != null) {
+                try {
+                    ActivityManager.getService()
+                            .appNotRespondingViaProvider(prc.holder.connection);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    private ProviderClientRecord installProviderAuthoritiesLocked(IContentProvider provider,
+            ContentProvider localProvider, ContentProviderHolder holder) {
+        final String auths[] = holder.info.authority.split(";");
+        final int userId = UserHandle.getUserId(holder.info.applicationInfo.uid);
+
+        if (provider != null) {
+            // If this provider is hosted by the core OS and cannot be upgraded,
+            // then I guess we're okay doing blocking calls to it.
+            for (String auth : auths) {
+                switch (auth) {
+                    case ContactsContract.AUTHORITY:
+                    case CallLog.AUTHORITY:
+                    case CallLog.SHADOW_AUTHORITY:
+                    case BlockedNumberContract.AUTHORITY:
+                    case CalendarContract.AUTHORITY:
+                    case Downloads.Impl.AUTHORITY:
+                    case "telephony":
+                        Binder.allowBlocking(provider.asBinder());
+                }
+            }
+        }
+
+        final ProviderClientRecord pcr = new ProviderClientRecord(
+                auths, provider, localProvider, holder);
+        for (String auth : auths) {
+            final ProviderKey key = new ProviderKey(auth, userId);
+            final ProviderClientRecord existing = mProviderMap.get(key);
+            if (existing != null) {
+                Slog.w(TAG, "Content provider " + pcr.mHolder.info.name
+                        + " already published as " + auth);
+            } else {
+                mProviderMap.put(key, pcr);
+            }
+        }
+        return pcr;
+    }
+
+    /**
+     * Installs the provider.
+     *
+     * Providers that are local to the process or that come from the system server
+     * may be installed permanently which is indicated by setting noReleaseNeeded to true.
+     * Other remote providers are reference counted.  The initial reference count
+     * for all reference counted providers is one.  Providers that are not reference
+     * counted do not have a reference count (at all).
+     *
+     * This method detects when a provider has already been installed.  When this happens,
+     * it increments the reference count of the existing provider (if appropriate)
+     * and returns the existing provider.  This can happen due to concurrent
+     * attempts to acquire the same provider.
+     */
+    @UnsupportedAppUsage
+    private ContentProviderHolder installProvider(Context context,
+            ContentProviderHolder holder, ProviderInfo info,
+            boolean noisy, boolean noReleaseNeeded, boolean stable) {
+        ContentProvider localProvider = null;
+        IContentProvider provider;
+        if (holder == null || holder.provider == null) {
+            if (DEBUG_PROVIDER || noisy) {
+                Slog.d(TAG, "Loading provider " + info.authority + ": "
+                        + info.name);
+            }
+            Context c = null;
+            ApplicationInfo ai = info.applicationInfo;
+            if (context.getPackageName().equals(ai.packageName)) {
+                c = context;
+            } else if (mInitialApplication != null &&
+                    mInitialApplication.getPackageName().equals(ai.packageName)) {
+                c = mInitialApplication;
+            } else {
+                try {
+                    c = context.createPackageContext(ai.packageName,
+                            Context.CONTEXT_INCLUDE_CODE);
+                } catch (PackageManager.NameNotFoundException e) {
+                    // Ignore
+                }
+            }
+            if (c == null) {
+                Slog.w(TAG, "Unable to get context for package " +
+                      ai.packageName +
+                      " while loading content provider " +
+                      info.name);
+                return null;
+            }
+
+            if (info.splitName != null) {
+                try {
+                    c = c.createContextForSplit(info.splitName);
+                } catch (NameNotFoundException e) {
+                    throw new RuntimeException(e);
+                }
+            }
+
+            try {
+                final java.lang.ClassLoader cl = c.getClassLoader();
+                LoadedApk packageInfo = peekPackageInfo(ai.packageName, true);
+                if (packageInfo == null) {
+                    // System startup case.
+                    packageInfo = getSystemContext().mPackageInfo;
+                }
+                localProvider = packageInfo.getAppFactory()
+                        .instantiateProvider(cl, info.name);
+                provider = localProvider.getIContentProvider();
+                if (provider == null) {
+                    Slog.e(TAG, "Failed to instantiate class " +
+                          info.name + " from sourceDir " +
+                          info.applicationInfo.sourceDir);
+                    return null;
+                }
+                if (DEBUG_PROVIDER) Slog.v(
+                    TAG, "Instantiating local provider " + info.name);
+                // XXX Need to create the correct context for this provider.
+                localProvider.attachInfo(c, info);
+            } catch (java.lang.Exception e) {
+                if (!mInstrumentation.onException(null, e)) {
+                    throw new RuntimeException(
+                            "Unable to get provider " + info.name
+                            + ": " + e.toString(), e);
+                }
+                return null;
+            }
+        } else {
+            provider = holder.provider;
+            if (DEBUG_PROVIDER) Slog.v(TAG, "Installing external provider " + info.authority + ": "
+                    + info.name);
+        }
+
+        ContentProviderHolder retHolder;
+
+        synchronized (mProviderMap) {
+            if (DEBUG_PROVIDER) Slog.v(TAG, "Checking to add " + provider
+                    + " / " + info.name);
+            IBinder jBinder = provider.asBinder();
+            if (localProvider != null) {
+                ComponentName cname = new ComponentName(info.packageName, info.name);
+                ProviderClientRecord pr = mLocalProvidersByName.get(cname);
+                if (pr != null) {
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "installProvider: lost the race, "
+                                + "using existing local provider");
+                    }
+                    provider = pr.mProvider;
+                } else {
+                    holder = new ContentProviderHolder(info);
+                    holder.provider = provider;
+                    holder.noReleaseNeeded = true;
+                    pr = installProviderAuthoritiesLocked(provider, localProvider, holder);
+                    mLocalProviders.put(jBinder, pr);
+                    mLocalProvidersByName.put(cname, pr);
+                }
+                retHolder = pr.mHolder;
+            } else {
+                ProviderRefCount prc = mProviderRefCountMap.get(jBinder);
+                if (prc != null) {
+                    if (DEBUG_PROVIDER) {
+                        Slog.v(TAG, "installProvider: lost the race, updating ref count");
+                    }
+                    // We need to transfer our new reference to the existing
+                    // ref count, releasing the old one...  but only if
+                    // release is needed (that is, it is not running in the
+                    // system process).
+                    if (!noReleaseNeeded) {
+                        incProviderRefLocked(prc, stable);
+                        try {
+                            ActivityManager.getService().removeContentProvider(
+                                    holder.connection, stable);
+                        } catch (RemoteException e) {
+                            //do nothing content provider object is dead any way
+                        }
+                    }
+                } else {
+                    ProviderClientRecord client = installProviderAuthoritiesLocked(
+                            provider, localProvider, holder);
+                    if (noReleaseNeeded) {
+                        prc = new ProviderRefCount(holder, client, 1000, 1000);
+                    } else {
+                        prc = stable
+                                ? new ProviderRefCount(holder, client, 1, 0)
+                                : new ProviderRefCount(holder, client, 0, 1);
+                    }
+                    mProviderRefCountMap.put(jBinder, prc);
+                }
+                retHolder = prc.holder;
+            }
+        }
+        return retHolder;
+    }
+
+    private void handleRunIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
+        try {
+            Method main = Class.forName(entryPoint).getMethod("main", String[].class);
+            main.invoke(null, new Object[]{entryPointArgs});
+        } catch (ReflectiveOperationException e) {
+            throw new AndroidRuntimeException("runIsolatedEntryPoint failed", e);
+        }
+        // The process will be empty after this method returns; exit the VM now.
+        System.exit(0);
+    }
+
+    @UnsupportedAppUsage
+    private void attach(boolean system, long startSeq) {
+        sCurrentActivityThread = this;
+        mSystemThread = system;
+        if (!system) {
+            android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
+                                                    UserHandle.myUserId());
+            RuntimeInit.setApplicationObject(mAppThread.asBinder());
+            final IActivityManager mgr = ActivityManager.getService();
+            try {
+                mgr.attachApplication(mAppThread, startSeq);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+            // Watch for getting close to heap limit.
+            BinderInternal.addGcWatcher(new Runnable() {
+                @Override public void run() {
+                    if (!mSomeActivitiesChanged) {
+                        return;
+                    }
+                    Runtime runtime = Runtime.getRuntime();
+                    long dalvikMax = runtime.maxMemory();
+                    long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
+                    if (dalvikUsed > ((3*dalvikMax)/4)) {
+                        if (DEBUG_MEMORY_TRIM) Slog.d(TAG, "Dalvik max=" + (dalvikMax/1024)
+                                + " total=" + (runtime.totalMemory()/1024)
+                                + " used=" + (dalvikUsed/1024));
+                        mSomeActivitiesChanged = false;
+                        try {
+                            ActivityTaskManager.getService().releaseSomeActivities(mAppThread);
+                        } catch (RemoteException e) {
+                            throw e.rethrowFromSystemServer();
+                        }
+                    }
+                }
+            });
+        } else {
+            // Don't set application object here -- if the system crashes,
+            // we can't display an alert, we just want to die die die.
+            android.ddm.DdmHandleAppName.setAppName("system_process",
+                    UserHandle.myUserId());
+            try {
+                mInstrumentation = new Instrumentation();
+                mInstrumentation.basicInit(this);
+                ContextImpl context = ContextImpl.createAppContext(
+                        this, getSystemContext().mPackageInfo);
+                mInitialApplication = context.mPackageInfo.makeApplication(true, null);
+                mInitialApplication.onCreate();
+            } catch (Exception e) {
+                throw new RuntimeException(
+                        "Unable to instantiate Application():" + e.toString(), e);
+            }
+        }
+
+        ViewRootImpl.ConfigChangedCallback configChangedCallback
+                = (Configuration globalConfig) -> {
+            synchronized (mResourcesManager) {
+                // We need to apply this change to the resources immediately, because upon returning
+                // the view hierarchy will be informed about it.
+                if (mResourcesManager.applyConfigurationToResourcesLocked(globalConfig,
+                        null /* compat */)) {
+                    updateLocaleListFromAppContext(mInitialApplication.getApplicationContext(),
+                            mResourcesManager.getConfiguration().getLocales());
+
+                    // This actually changed the resources! Tell everyone about it.
+                    if (mPendingConfiguration == null
+                            || mPendingConfiguration.isOtherSeqNewer(globalConfig)) {
+                        mPendingConfiguration = globalConfig;
+                        sendMessage(H.CONFIGURATION_CHANGED, globalConfig);
+                    }
+                }
+            }
+        };
+        ViewRootImpl.addConfigCallback(configChangedCallback);
+    }
+
+    @UnsupportedAppUsage
+    public static ActivityThread systemMain() {
+        // The system process on low-memory devices do not get to use hardware
+        // accelerated drawing, since this can add too much overhead to the
+        // process.
+        if (!ActivityManager.isHighEndGfx()) {
+            ThreadedRenderer.disable(true);
+        } else {
+            ThreadedRenderer.enableForegroundTrimming();
+        }
+        ActivityThread thread = new ActivityThread();
+        thread.attach(true, 0);
+        return thread;
+    }
+
+    public static void updateHttpProxy(@NonNull Context context) {
+        final ConnectivityManager cm = ConnectivityManager.from(context);
+        Proxy.setHttpProxySystemProperty(cm.getDefaultProxy());
+    }
+
+    @UnsupportedAppUsage
+    public final void installSystemProviders(List<ProviderInfo> providers) {
+        if (providers != null) {
+            installContentProviders(mInitialApplication, providers);
+        }
+    }
+
+    public int getIntCoreSetting(String key, int defaultValue) {
+        synchronized (mResourcesManager) {
+            if (mCoreSettings != null) {
+                return mCoreSettings.getInt(key, defaultValue);
+            }
+            return defaultValue;
+        }
+    }
+
+    private static class AndroidOs extends ForwardingOs {
+        /**
+         * Install selective syscall interception. For example, this is used to
+         * implement special filesystem paths that will be redirected to
+         * {@link ContentResolver#openFileDescriptor(Uri, String)}.
+         */
+        public static void install() {
+            // If feature is disabled, we don't need to install
+            if (!DEPRECATE_DATA_COLUMNS) return;
+
+            // Install interception and make sure it sticks!
+            Os def = null;
+            do {
+                def = Os.getDefault();
+            } while (!Os.compareAndSetDefault(def, new AndroidOs(def)));
+        }
+
+        private AndroidOs(Os os) {
+            super(os);
+        }
+
+        private FileDescriptor openDeprecatedDataPath(String path, int mode) throws ErrnoException {
+            final Uri uri = ContentResolver.translateDeprecatedDataPath(path);
+            Log.v(TAG, "Redirecting " + path + " to " + uri);
+
+            final ContentResolver cr = currentActivityThread().getApplication()
+                    .getContentResolver();
+            try {
+                final FileDescriptor fd = new FileDescriptor();
+                fd.setInt$(cr.openFileDescriptor(uri,
+                        FileUtils.translateModePosixToString(mode)).detachFd());
+                return fd;
+            } catch (SecurityException e) {
+                throw new ErrnoException(e.getMessage(), OsConstants.EACCES);
+            } catch (FileNotFoundException e) {
+                throw new ErrnoException(e.getMessage(), OsConstants.ENOENT);
+            }
+        }
+
+        private void deleteDeprecatedDataPath(String path) throws ErrnoException {
+            final Uri uri = ContentResolver.translateDeprecatedDataPath(path);
+            Log.v(TAG, "Redirecting " + path + " to " + uri);
+
+            final ContentResolver cr = currentActivityThread().getApplication()
+                    .getContentResolver();
+            try {
+                if (cr.delete(uri, null, null) == 0) {
+                    throw new FileNotFoundException();
+                }
+            } catch (SecurityException e) {
+                throw new ErrnoException(e.getMessage(), OsConstants.EACCES);
+            } catch (FileNotFoundException e) {
+                throw new ErrnoException(e.getMessage(), OsConstants.ENOENT);
+            }
+        }
+
+        @Override
+        public boolean access(String path, int mode) throws ErrnoException {
+            if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) {
+                // If we opened it okay, then access check succeeded
+                IoUtils.closeQuietly(
+                        openDeprecatedDataPath(path, FileUtils.translateModeAccessToPosix(mode)));
+                return true;
+            } else {
+                return super.access(path, mode);
+            }
+        }
+
+        @Override
+        public FileDescriptor open(String path, int flags, int mode) throws ErrnoException {
+            if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) {
+                return openDeprecatedDataPath(path, mode);
+            } else {
+                return super.open(path, flags, mode);
+            }
+        }
+
+        @Override
+        public StructStat stat(String path) throws ErrnoException {
+            if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) {
+                final FileDescriptor fd = openDeprecatedDataPath(path, OsConstants.O_RDONLY);
+                try {
+                    return android.system.Os.fstat(fd);
+                } finally {
+                    IoUtils.closeQuietly(fd);
+                }
+            } else {
+                return super.stat(path);
+            }
+        }
+
+        @Override
+        public void unlink(String path) throws ErrnoException {
+            if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) {
+                deleteDeprecatedDataPath(path);
+            } else {
+                super.unlink(path);
+            }
+        }
+
+        @Override
+        public void remove(String path) throws ErrnoException {
+            if (path != null && path.startsWith(DEPRECATE_DATA_PREFIX)) {
+                deleteDeprecatedDataPath(path);
+            } else {
+                super.remove(path);
+            }
+        }
+
+        @Override
+        public void rename(String oldPath, String newPath) throws ErrnoException {
+            try {
+                super.rename(oldPath, newPath);
+            } catch (ErrnoException e) {
+                if (e.errno == OsConstants.EXDEV) {
+                    Log.v(TAG, "Recovering failed rename " + oldPath + " to " + newPath);
+                    try {
+                        Files.move(new File(oldPath).toPath(), new File(newPath).toPath());
+                    } catch (IOException e2) {
+                        throw e;
+                    }
+                } else {
+                    throw e;
+                }
+            }
+        }
+    }
+
+    public static void main(String[] args) {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
+
+        // Install selective syscall interception
+        AndroidOs.install();
+
+        // CloseGuard defaults to true and can be quite spammy.  We
+        // disable it here, but selectively enable it later (via
+        // StrictMode) on debug builds, but using DropBox, not logs.
+        CloseGuard.setEnabled(false);
+
+        Environment.initForCurrentUser();
+
+        // Make sure TrustedCertificateStore looks in the right place for CA certificates
+        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
+        TrustedCertificateStore.setDefaultUserDirectory(configDir);
+
+        Process.setArgV0("<pre-initialized>");
+
+        Looper.prepareMainLooper();
+
+        // Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
+        // It will be in the format "seq=114"
+        long startSeq = 0;
+        if (args != null) {
+            for (int i = args.length - 1; i >= 0; --i) {
+                if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
+                    startSeq = Long.parseLong(
+                            args[i].substring(PROC_START_SEQ_IDENT.length()));
+                }
+            }
+        }
+        ActivityThread thread = new ActivityThread();
+        thread.attach(false, startSeq);
+
+        if (sMainThreadHandler == null) {
+            sMainThreadHandler = thread.getHandler();
+        }
+
+        if (false) {
+            Looper.myLooper().setMessageLogging(new
+                    LogPrinter(Log.DEBUG, "ActivityThread"));
+        }
+
+        // End of event ActivityThreadMain.
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+        Looper.loop();
+
+        throw new RuntimeException("Main thread loop unexpectedly exited");
+    }
+
+    private void purgePendingResources() {
+        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "purgePendingResources");
+        nPurgePendingResources();
+        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+    }
+
+    // ------------------ Regular JNI ------------------------
+    private native void nPurgePendingResources();
+    private native void nDumpGraphicsInfo(FileDescriptor fd);
+    private native void nInitZygoteChildHeapProfiling();
+}
diff --git a/android/app/ActivityTransitionCoordinator.java b/android/app/ActivityTransitionCoordinator.java
new file mode 100644
index 0000000..4b87a64
--- /dev/null
+++ b/android/app/ActivityTransitionCoordinator.java
@@ -0,0 +1,1121 @@
+/*
+ * 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.app;
+
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.ResultReceiver;
+import android.transition.Transition;
+import android.transition.TransitionListenerAdapter;
+import android.transition.TransitionSet;
+import android.transition.Visibility;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.view.GhostView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroupOverlay;
+import android.view.ViewParent;
+import android.view.ViewRootImpl;
+import android.view.ViewTreeObserver;
+import android.view.Window;
+import android.widget.ImageView;
+
+import com.android.internal.view.OneShotPreDrawListener;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Base class for ExitTransitionCoordinator and EnterTransitionCoordinator, classes
+ * that manage activity transitions and the communications coordinating them between
+ * Activities. The ExitTransitionCoordinator is created in the
+ * ActivityOptions#makeSceneTransitionAnimation. The EnterTransitionCoordinator
+ * is created by ActivityOptions#createEnterActivityTransition by Activity when the window is
+ * attached.
+ *
+ * Typical startActivity goes like this:
+ * 1) ExitTransitionCoordinator created with ActivityOptions#makeSceneTransitionAnimation
+ * 2) Activity#startActivity called and that calls startExit() through
+ *    ActivityOptions#dispatchStartExit
+ *    - Exit transition starts by setting transitioning Views to INVISIBLE
+ * 3) Launched Activity starts, creating an EnterTransitionCoordinator.
+ *    - The Window is made translucent
+ *    - The Window background alpha is set to 0
+ *    - The transitioning views are made INVISIBLE
+ *    - MSG_SET_LISTENER is sent back to the ExitTransitionCoordinator.
+ * 4) The shared element transition completes.
+ *    - MSG_TAKE_SHARED_ELEMENTS is sent to the EnterTransitionCoordinator
+ * 5) The MSG_TAKE_SHARED_ELEMENTS is received by the EnterTransitionCoordinator.
+ *    - Shared elements are made VISIBLE
+ *    - Shared elements positions and size are set to match the end state of the calling
+ *      Activity.
+ *    - The shared element transition is started
+ *    - If the window allows overlapping transitions, the views transition is started by setting
+ *      the entering Views to VISIBLE and the background alpha is animated to opaque.
+ *    - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
+ * 6) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
+ *    - The shared elements are made INVISIBLE
+ * 7) The exit transition completes in the calling Activity.
+ *    - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
+ * 8) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
+ *    - If the window doesn't allow overlapping enter transitions, the enter transition is started
+ *      by setting entering views to VISIBLE and the background is animated to opaque.
+ * 9) The background opacity animation completes.
+ *    - The window is made opaque
+ * 10) The calling Activity gets an onStop() call
+ *    - onActivityStopped() is called and all exited Views are made VISIBLE.
+ *
+ * Typical finishAfterTransition goes like this:
+ * 1) finishAfterTransition() creates an ExitTransitionCoordinator and calls startExit()
+ *    - The Window start transitioning to Translucent with a new ActivityOptions.
+ *    - If no background exists, a black background is substituted
+ *    - The shared elements in the scene are matched against those shared elements
+ *      that were sent by comparing the names.
+ *    - The exit transition is started by setting Views to INVISIBLE.
+ * 2) The ActivityOptions is received by the Activity and an EnterTransitionCoordinator is created.
+ *    - All transitioning views are made VISIBLE to reverse what was done when onActivityStopped()
+ *      was called
+ * 3) The Window is made translucent and a callback is received
+ *    - The background alpha is animated to 0
+ * 4) The background alpha animation completes
+ * 5) The shared element transition completes
+ *    - After both 4 & 5 complete, MSG_TAKE_SHARED_ELEMENTS is sent to the
+ *      EnterTransitionCoordinator
+ * 6) MSG_TAKE_SHARED_ELEMENTS is received by EnterTransitionCoordinator
+ *    - Shared elements are made VISIBLE
+ *    - Shared elements positions and size are set to match the end state of the calling
+ *      Activity.
+ *    - The shared element transition is started
+ *    - If the window allows overlapping transitions, the views transition is started by setting
+ *      the entering Views to VISIBLE.
+ *    - MSG_HIDE_SHARED_ELEMENTS is sent to the ExitTransitionCoordinator
+ * 7) MSG_HIDE_SHARED_ELEMENTS is received by the ExitTransitionCoordinator
+ *    - The shared elements are made INVISIBLE
+ * 8) The exit transition completes in the finishing Activity.
+ *    - MSG_EXIT_TRANSITION_COMPLETE is sent to the EnterTransitionCoordinator.
+ *    - finish() is called on the exiting Activity
+ * 9) The MSG_EXIT_TRANSITION_COMPLETE is received by the EnterTransitionCoordinator.
+ *    - If the window doesn't allow overlapping enter transitions, the enter transition is started
+ *      by setting entering views to VISIBLE.
+ */
+abstract class ActivityTransitionCoordinator extends ResultReceiver {
+    private static final String TAG = "ActivityTransitionCoordinator";
+
+    /**
+     * For Activity transitions, the called Activity's listener to receive calls
+     * when transitions complete.
+     */
+    static final String KEY_REMOTE_RECEIVER = "android:remoteReceiver";
+
+    protected static final String KEY_SCREEN_LEFT = "shared_element:screenLeft";
+    protected static final String KEY_SCREEN_TOP = "shared_element:screenTop";
+    protected static final String KEY_SCREEN_RIGHT = "shared_element:screenRight";
+    protected static final String KEY_SCREEN_BOTTOM= "shared_element:screenBottom";
+    protected static final String KEY_TRANSLATION_Z = "shared_element:translationZ";
+    protected static final String KEY_SNAPSHOT = "shared_element:bitmap";
+    protected static final String KEY_SCALE_TYPE = "shared_element:scaleType";
+    protected static final String KEY_IMAGE_MATRIX = "shared_element:imageMatrix";
+    protected static final String KEY_ELEVATION = "shared_element:elevation";
+
+    protected static final ImageView.ScaleType[] SCALE_TYPE_VALUES = ImageView.ScaleType.values();
+
+    /**
+     * Sent by the exiting coordinator (either EnterTransitionCoordinator
+     * or ExitTransitionCoordinator) after the shared elements have
+     * become stationary (shared element transition completes). This tells
+     * the remote coordinator to take control of the shared elements and
+     * that animations may begin. The remote Activity won't start entering
+     * until this message is received, but may wait for
+     * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
+     */
+    public static final int MSG_SET_REMOTE_RECEIVER = 100;
+
+    /**
+     * Sent by the entering coordinator to tell the exiting coordinator
+     * to hide its shared elements after it has started its shared
+     * element transition. This is temporary until the
+     * interlock of shared elements is figured out.
+     */
+    public static final int MSG_HIDE_SHARED_ELEMENTS = 101;
+
+    /**
+     * Sent by the exiting coordinator (either EnterTransitionCoordinator
+     * or ExitTransitionCoordinator) after the shared elements have
+     * become stationary (shared element transition completes). This tells
+     * the remote coordinator to take control of the shared elements and
+     * that animations may begin. The remote Activity won't start entering
+     * until this message is received, but may wait for
+     * MSG_EXIT_TRANSITION_COMPLETE if allowOverlappingTransitions() is true.
+     */
+    public static final int MSG_TAKE_SHARED_ELEMENTS = 103;
+
+    /**
+     * Sent by the exiting coordinator (either
+     * EnterTransitionCoordinator or ExitTransitionCoordinator) after
+     * the exiting Views have finished leaving the scene. This will
+     * be ignored if allowOverlappingTransitions() is true on the
+     * remote coordinator. If it is false, it will trigger the enter
+     * transition to start.
+     */
+    public static final int MSG_EXIT_TRANSITION_COMPLETE = 104;
+
+    /**
+     * Sent by Activity#startActivity to begin the exit transition.
+     */
+    public static final int MSG_START_EXIT_TRANSITION = 105;
+
+    /**
+     * It took too long for a message from the entering Activity, so we canceled the transition.
+     */
+    public static final int MSG_CANCEL = 106;
+
+    /**
+     * When returning, this is the destination location for the shared element.
+     */
+    public static final int MSG_SHARED_ELEMENT_DESTINATION = 107;
+
+    /**
+     * Sent by Activity#startActivity to notify the entering activity that enter animation for
+     * back is allowed. If this message is not received, the default exit animation will run when
+     * backing out of an activity (instead of the 'reverse' shared element transition).
+     */
+    public static final int MSG_ALLOW_RETURN_TRANSITION = 108;
+
+    private Window mWindow;
+    final protected ArrayList<String> mAllSharedElementNames;
+    final protected ArrayList<View> mSharedElements = new ArrayList<View>();
+    final protected ArrayList<String> mSharedElementNames = new ArrayList<String>();
+    protected ArrayList<View> mTransitioningViews = new ArrayList<View>();
+    protected SharedElementCallback mListener;
+    protected ResultReceiver mResultReceiver;
+    final private FixedEpicenterCallback mEpicenterCallback = new FixedEpicenterCallback();
+    final protected boolean mIsReturning;
+    private Runnable mPendingTransition;
+    private boolean mIsStartingTransition;
+    private ArrayList<GhostViewListeners> mGhostViewListeners =
+            new ArrayList<GhostViewListeners>();
+    private ArrayMap<View, Float> mOriginalAlphas = new ArrayMap<View, Float>();
+    private ArrayList<Matrix> mSharedElementParentMatrices;
+    private boolean mSharedElementTransitionComplete;
+    private boolean mViewsTransitionComplete;
+    private boolean mBackgroundAnimatorComplete;
+    private ArrayList<View> mStrippedTransitioningViews = new ArrayList<>();
+
+    public ActivityTransitionCoordinator(Window window,
+            ArrayList<String> allSharedElementNames,
+            SharedElementCallback listener, boolean isReturning) {
+        super(new Handler());
+        mWindow = window;
+        mListener = listener;
+        mAllSharedElementNames = allSharedElementNames;
+        mIsReturning = isReturning;
+    }
+
+    protected void viewsReady(ArrayMap<String, View> sharedElements) {
+        sharedElements.retainAll(mAllSharedElementNames);
+        if (mListener != null) {
+            mListener.onMapSharedElements(mAllSharedElementNames, sharedElements);
+        }
+        setSharedElements(sharedElements);
+        if (getViewsTransition() != null && mTransitioningViews != null) {
+            ViewGroup decorView = getDecor();
+            if (decorView != null) {
+                decorView.captureTransitioningViews(mTransitioningViews);
+            }
+            mTransitioningViews.removeAll(mSharedElements);
+        }
+        setEpicenter();
+    }
+
+    /**
+     * Iterates over the shared elements and adds them to the members in order.
+     * Shared elements that are nested in other shared elements are placed after the
+     * elements that they are nested in. This means that layout ordering can be done
+     * from first to last.
+     *
+     * @param sharedElements The map of transition names to shared elements to set into
+     *                       the member fields.
+     */
+    private void setSharedElements(ArrayMap<String, View> sharedElements) {
+        boolean isFirstRun = true;
+        while (!sharedElements.isEmpty()) {
+            final int numSharedElements = sharedElements.size();
+            for (int i = numSharedElements - 1; i >= 0; i--) {
+                final View view = sharedElements.valueAt(i);
+                final String name = sharedElements.keyAt(i);
+                if (isFirstRun && (view == null || !view.isAttachedToWindow() || name == null)) {
+                    sharedElements.removeAt(i);
+                } else if (!isNested(view, sharedElements)) {
+                    mSharedElementNames.add(name);
+                    mSharedElements.add(view);
+                    sharedElements.removeAt(i);
+                }
+            }
+            isFirstRun = false;
+        }
+    }
+
+    /**
+     * Returns true when view is nested in any of the values of sharedElements.
+     */
+    private static boolean isNested(View view, ArrayMap<String, View> sharedElements) {
+        ViewParent parent = view.getParent();
+        boolean isNested = false;
+        while (parent instanceof View) {
+            View parentView = (View) parent;
+            if (sharedElements.containsValue(parentView)) {
+                isNested = true;
+                break;
+            }
+            parent = parentView.getParent();
+        }
+        return isNested;
+    }
+
+    protected void stripOffscreenViews() {
+        if (mTransitioningViews == null) {
+            return;
+        }
+        Rect r = new Rect();
+        for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
+            View view = mTransitioningViews.get(i);
+            if (!view.getGlobalVisibleRect(r)) {
+                mTransitioningViews.remove(i);
+                mStrippedTransitioningViews.add(view);
+            }
+        }
+    }
+
+    protected Window getWindow() {
+        return mWindow;
+    }
+
+    public ViewGroup getDecor() {
+        return (mWindow == null) ? null : (ViewGroup) mWindow.getDecorView();
+    }
+
+    /**
+     * Sets the transition epicenter to the position of the first shared element.
+     */
+    protected void setEpicenter() {
+        View epicenter = null;
+        if (!mAllSharedElementNames.isEmpty() && !mSharedElementNames.isEmpty()) {
+            int index = mSharedElementNames.indexOf(mAllSharedElementNames.get(0));
+            if (index >= 0) {
+                epicenter = mSharedElements.get(index);
+            }
+        }
+        setEpicenter(epicenter);
+    }
+
+    private void setEpicenter(View view) {
+        if (view == null) {
+            mEpicenterCallback.setEpicenter(null);
+        } else {
+            Rect epicenter = new Rect();
+            view.getBoundsOnScreen(epicenter);
+            mEpicenterCallback.setEpicenter(epicenter);
+        }
+    }
+
+    public ArrayList<String> getAcceptedNames() {
+        return mSharedElementNames;
+    }
+
+    public ArrayList<String> getMappedNames() {
+        ArrayList<String> names = new ArrayList<String>(mSharedElements.size());
+        for (int i = 0; i < mSharedElements.size(); i++) {
+            names.add(mSharedElements.get(i).getTransitionName());
+        }
+        return names;
+    }
+
+    public ArrayList<View> copyMappedViews() {
+        return new ArrayList<View>(mSharedElements);
+    }
+
+    protected Transition setTargets(Transition transition, boolean add) {
+        if (transition == null || (add &&
+                (mTransitioningViews == null || mTransitioningViews.isEmpty()))) {
+            return null;
+        }
+        // Add the targets to a set containing transition so that transition
+        // remains unaffected. We don't want to modify the targets of transition itself.
+        TransitionSet set = new TransitionSet();
+        if (mTransitioningViews != null) {
+            for (int i = mTransitioningViews.size() - 1; i >= 0; i--) {
+                View view = mTransitioningViews.get(i);
+                if (add) {
+                    set.addTarget(view);
+                } else {
+                    set.excludeTarget(view, true);
+                }
+            }
+        }
+        if (mStrippedTransitioningViews != null) {
+            for (int i = mStrippedTransitioningViews.size() - 1; i >= 0; i--) {
+                View view = mStrippedTransitioningViews.get(i);
+                set.excludeTarget(view, true);
+            }
+        }
+        // By adding the transition after addTarget, we prevent addTarget from
+        // affecting transition.
+        set.addTransition(transition);
+
+        if (!add && mTransitioningViews != null && !mTransitioningViews.isEmpty()) {
+            // Allow children of excluded transitioning views, but not the views themselves
+            set = new TransitionSet().addTransition(set);
+        }
+
+        return set;
+    }
+
+    protected Transition configureTransition(Transition transition,
+            boolean includeTransitioningViews) {
+        if (transition != null) {
+            transition = transition.clone();
+            transition.setEpicenterCallback(mEpicenterCallback);
+            transition = setTargets(transition, includeTransitioningViews);
+        }
+        noLayoutSuppressionForVisibilityTransitions(transition);
+        return transition;
+    }
+
+    /**
+     * Looks through the transition to see which Views have been included and which have been
+     * excluded. {@code views} will be modified to contain only those Views that are included
+     * in the transition. If {@code transition} is a TransitionSet, it will search through all
+     * contained Transitions to find targeted Views.
+     *
+     * @param transition The transition to look through for inclusion of Views
+     * @param views The list of Views that are to be checked for inclusion. Will be modified
+     *              to remove all excluded Views, possibly leaving an empty list.
+     */
+    protected static void removeExcludedViews(Transition transition, ArrayList<View> views) {
+        ArraySet<View> included = new ArraySet<>();
+        findIncludedViews(transition, views, included);
+        views.clear();
+        views.addAll(included);
+    }
+
+    /**
+     * Looks through the transition to see which Views have been included. Only {@code views}
+     * will be examined for inclusion. If {@code transition} is a TransitionSet, it will search
+     * through all contained Transitions to find targeted Views.
+     *
+     * @param transition The transition to look through for inclusion of Views
+     * @param views The list of Views that are to be checked for inclusion.
+     * @param included Modified to contain all Views in views that have at least one Transition
+     *                 that affects it.
+     */
+    private static void findIncludedViews(Transition transition, ArrayList<View> views,
+            ArraySet<View> included) {
+        if (transition instanceof TransitionSet) {
+            TransitionSet set = (TransitionSet) transition;
+            ArrayList<View> includedViews = new ArrayList<>();
+            final int numViews = views.size();
+            for (int i = 0; i < numViews; i++) {
+                final View view = views.get(i);
+                if (transition.isValidTarget(view)) {
+                    includedViews.add(view);
+                }
+            }
+            final int count = set.getTransitionCount();
+            for (int i = 0; i < count; i++) {
+                findIncludedViews(set.getTransitionAt(i), includedViews, included);
+            }
+        } else {
+            final int numViews = views.size();
+            for (int i = 0; i < numViews; i++) {
+                final View view = views.get(i);
+                if (transition.isValidTarget(view)) {
+                    included.add(view);
+                }
+            }
+        }
+    }
+
+    protected static Transition mergeTransitions(Transition transition1, Transition transition2) {
+        if (transition1 == null) {
+            return transition2;
+        } else if (transition2 == null) {
+            return transition1;
+        } else {
+            TransitionSet transitionSet = new TransitionSet();
+            transitionSet.addTransition(transition1);
+            transitionSet.addTransition(transition2);
+            return transitionSet;
+        }
+    }
+
+    protected ArrayMap<String, View> mapSharedElements(ArrayList<String> accepted,
+            ArrayList<View> localViews) {
+        ArrayMap<String, View> sharedElements = new ArrayMap<String, View>();
+        if (accepted != null) {
+            for (int i = 0; i < accepted.size(); i++) {
+                sharedElements.put(accepted.get(i), localViews.get(i));
+            }
+        } else {
+            ViewGroup decorView = getDecor();
+            if (decorView != null) {
+                decorView.findNamedViews(sharedElements);
+            }
+        }
+        return sharedElements;
+    }
+
+    protected void setResultReceiver(ResultReceiver resultReceiver) {
+        mResultReceiver = resultReceiver;
+    }
+
+    protected abstract Transition getViewsTransition();
+
+    private void setSharedElementState(View view, String name, Bundle transitionArgs,
+            Matrix tempMatrix, RectF tempRect, int[] decorLoc) {
+        Bundle sharedElementBundle = transitionArgs.getBundle(name);
+        if (sharedElementBundle == null) {
+            return;
+        }
+
+        if (view instanceof ImageView) {
+            int scaleTypeInt = sharedElementBundle.getInt(KEY_SCALE_TYPE, -1);
+            if (scaleTypeInt >= 0) {
+                ImageView imageView = (ImageView) view;
+                ImageView.ScaleType scaleType = SCALE_TYPE_VALUES[scaleTypeInt];
+                imageView.setScaleType(scaleType);
+                if (scaleType == ImageView.ScaleType.MATRIX) {
+                    float[] matrixValues = sharedElementBundle.getFloatArray(KEY_IMAGE_MATRIX);
+                    tempMatrix.setValues(matrixValues);
+                    imageView.setImageMatrix(tempMatrix);
+                }
+            }
+        }
+
+        float z = sharedElementBundle.getFloat(KEY_TRANSLATION_Z);
+        view.setTranslationZ(z);
+        float elevation = sharedElementBundle.getFloat(KEY_ELEVATION);
+        view.setElevation(elevation);
+
+        float left = sharedElementBundle.getFloat(KEY_SCREEN_LEFT);
+        float top = sharedElementBundle.getFloat(KEY_SCREEN_TOP);
+        float right = sharedElementBundle.getFloat(KEY_SCREEN_RIGHT);
+        float bottom = sharedElementBundle.getFloat(KEY_SCREEN_BOTTOM);
+
+        if (decorLoc != null) {
+            left -= decorLoc[0];
+            top -= decorLoc[1];
+            right -= decorLoc[0];
+            bottom -= decorLoc[1];
+        } else {
+            // Find the location in the view's parent
+            getSharedElementParentMatrix(view, tempMatrix);
+            tempRect.set(left, top, right, bottom);
+            tempMatrix.mapRect(tempRect);
+
+            float leftInParent = tempRect.left;
+            float topInParent = tempRect.top;
+
+            // Find the size of the view
+            view.getInverseMatrix().mapRect(tempRect);
+            float width = tempRect.width();
+            float height = tempRect.height();
+
+            // Now determine the offset due to view transform:
+            view.setLeft(0);
+            view.setTop(0);
+            view.setRight(Math.round(width));
+            view.setBottom(Math.round(height));
+            tempRect.set(0, 0, width, height);
+            view.getMatrix().mapRect(tempRect);
+
+            left = leftInParent - tempRect.left;
+            top = topInParent - tempRect.top;
+            right = left + width;
+            bottom = top + height;
+        }
+
+        int x = Math.round(left);
+        int y = Math.round(top);
+        int width = Math.round(right) - x;
+        int height = Math.round(bottom) - y;
+        int widthSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
+        int heightSpec = View.MeasureSpec.makeMeasureSpec(height, View.MeasureSpec.EXACTLY);
+        view.measure(widthSpec, heightSpec);
+
+        view.layout(x, y, x + width, y + height);
+    }
+
+    private void setSharedElementMatrices() {
+        int numSharedElements = mSharedElements.size();
+        if (numSharedElements > 0) {
+            mSharedElementParentMatrices = new ArrayList<Matrix>(numSharedElements);
+        }
+        for (int i = 0; i < numSharedElements; i++) {
+            View view = mSharedElements.get(i);
+
+            // Find the location in the view's parent
+            ViewGroup parent = (ViewGroup) view.getParent();
+            Matrix matrix = new Matrix();
+            if (parent != null) {
+                parent.transformMatrixToLocal(matrix);
+                matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
+            }
+            mSharedElementParentMatrices.add(matrix);
+        }
+    }
+
+    private void getSharedElementParentMatrix(View view, Matrix matrix) {
+        final int index = mSharedElementParentMatrices == null ? -1
+                : mSharedElements.indexOf(view);
+        if (index < 0) {
+            matrix.reset();
+            ViewParent viewParent = view.getParent();
+            if (viewParent instanceof ViewGroup) {
+                // Find the location in the view's parent
+                ViewGroup parent = (ViewGroup) viewParent;
+                parent.transformMatrixToLocal(matrix);
+                matrix.postTranslate(parent.getScrollX(), parent.getScrollY());
+            }
+        } else {
+            // The indices of mSharedElementParentMatrices matches the
+            // mSharedElement matrices.
+            Matrix parentMatrix = mSharedElementParentMatrices.get(index);
+            matrix.set(parentMatrix);
+        }
+    }
+
+    protected ArrayList<SharedElementOriginalState> setSharedElementState(
+            Bundle sharedElementState, final ArrayList<View> snapshots) {
+        ArrayList<SharedElementOriginalState> originalImageState =
+                new ArrayList<SharedElementOriginalState>();
+        if (sharedElementState != null) {
+            Matrix tempMatrix = new Matrix();
+            RectF tempRect = new RectF();
+            final int numSharedElements = mSharedElements.size();
+            for (int i = 0; i < numSharedElements; i++) {
+                View sharedElement = mSharedElements.get(i);
+                String name = mSharedElementNames.get(i);
+                SharedElementOriginalState originalState = getOldSharedElementState(sharedElement,
+                        name, sharedElementState);
+                originalImageState.add(originalState);
+                setSharedElementState(sharedElement, name, sharedElementState,
+                        tempMatrix, tempRect, null);
+            }
+        }
+        if (mListener != null) {
+            mListener.onSharedElementStart(mSharedElementNames, mSharedElements, snapshots);
+        }
+        return originalImageState;
+    }
+
+    protected void notifySharedElementEnd(ArrayList<View> snapshots) {
+        if (mListener != null) {
+            mListener.onSharedElementEnd(mSharedElementNames, mSharedElements, snapshots);
+        }
+    }
+
+    protected void scheduleSetSharedElementEnd(final ArrayList<View> snapshots) {
+        final View decorView = getDecor();
+        if (decorView != null) {
+            OneShotPreDrawListener.add(decorView, () -> {
+                notifySharedElementEnd(snapshots);
+            });
+        }
+    }
+
+    private static SharedElementOriginalState getOldSharedElementState(View view, String name,
+            Bundle transitionArgs) {
+
+        SharedElementOriginalState state = new SharedElementOriginalState();
+        state.mLeft = view.getLeft();
+        state.mTop = view.getTop();
+        state.mRight = view.getRight();
+        state.mBottom = view.getBottom();
+        state.mMeasuredWidth = view.getMeasuredWidth();
+        state.mMeasuredHeight = view.getMeasuredHeight();
+        state.mTranslationZ = view.getTranslationZ();
+        state.mElevation = view.getElevation();
+        if (!(view instanceof ImageView)) {
+            return state;
+        }
+        Bundle bundle = transitionArgs.getBundle(name);
+        if (bundle == null) {
+            return state;
+        }
+        int scaleTypeInt = bundle.getInt(KEY_SCALE_TYPE, -1);
+        if (scaleTypeInt < 0) {
+            return state;
+        }
+
+        ImageView imageView = (ImageView) view;
+        state.mScaleType = imageView.getScaleType();
+        if (state.mScaleType == ImageView.ScaleType.MATRIX) {
+            state.mMatrix = new Matrix(imageView.getImageMatrix());
+        }
+        return state;
+    }
+
+    protected ArrayList<View> createSnapshots(Bundle state, Collection<String> names) {
+        int numSharedElements = names.size();
+        ArrayList<View> snapshots = new ArrayList<View>(numSharedElements);
+        if (numSharedElements == 0) {
+            return snapshots;
+        }
+        Context context = getWindow().getContext();
+        int[] decorLoc = new int[2];
+        ViewGroup decorView = getDecor();
+        if (decorView != null) {
+            decorView.getLocationOnScreen(decorLoc);
+        }
+        Matrix tempMatrix = new Matrix();
+        for (String name: names) {
+            Bundle sharedElementBundle = state.getBundle(name);
+            View snapshot = null;
+            if (sharedElementBundle != null) {
+                Parcelable parcelable = sharedElementBundle.getParcelable(KEY_SNAPSHOT);
+                if (parcelable != null && mListener != null) {
+                    snapshot = mListener.onCreateSnapshotView(context, parcelable);
+                }
+                if (snapshot != null) {
+                    setSharedElementState(snapshot, name, state, tempMatrix, null, decorLoc);
+                }
+            }
+            // Even null snapshots are added so they remain in the same order as shared elements.
+            snapshots.add(snapshot);
+        }
+        return snapshots;
+    }
+
+    protected static void setOriginalSharedElementState(ArrayList<View> sharedElements,
+            ArrayList<SharedElementOriginalState> originalState) {
+        for (int i = 0; i < originalState.size(); i++) {
+            View view = sharedElements.get(i);
+            SharedElementOriginalState state = originalState.get(i);
+            if (view instanceof ImageView && state.mScaleType != null) {
+                ImageView imageView = (ImageView) view;
+                imageView.setScaleType(state.mScaleType);
+                if (state.mScaleType == ImageView.ScaleType.MATRIX) {
+                  imageView.setImageMatrix(state.mMatrix);
+                }
+            }
+            view.setElevation(state.mElevation);
+            view.setTranslationZ(state.mTranslationZ);
+            int widthSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredWidth,
+                    View.MeasureSpec.EXACTLY);
+            int heightSpec = View.MeasureSpec.makeMeasureSpec(state.mMeasuredHeight,
+                    View.MeasureSpec.EXACTLY);
+            view.measure(widthSpec, heightSpec);
+            view.layout(state.mLeft, state.mTop, state.mRight, state.mBottom);
+        }
+    }
+
+    protected Bundle captureSharedElementState() {
+        Bundle bundle = new Bundle();
+        RectF tempBounds = new RectF();
+        Matrix tempMatrix = new Matrix();
+        for (int i = 0; i < mSharedElements.size(); i++) {
+            View sharedElement = mSharedElements.get(i);
+            String name = mSharedElementNames.get(i);
+            captureSharedElementState(sharedElement, name, bundle, tempMatrix, tempBounds);
+        }
+        return bundle;
+    }
+
+    protected void clearState() {
+        // Clear the state so that we can't hold any references accidentally and leak memory.
+        mWindow = null;
+        mSharedElements.clear();
+        mTransitioningViews = null;
+        mStrippedTransitioningViews = null;
+        mOriginalAlphas.clear();
+        mResultReceiver = null;
+        mPendingTransition = null;
+        mListener = null;
+        mSharedElementParentMatrices = null;
+    }
+
+    protected long getFadeDuration() {
+        return getWindow().getTransitionBackgroundFadeDuration();
+    }
+
+    protected void hideViews(ArrayList<View> views) {
+        int count = views.size();
+        for (int i = 0; i < count; i++) {
+            View view = views.get(i);
+            if (!mOriginalAlphas.containsKey(view)) {
+                mOriginalAlphas.put(view, view.getAlpha());
+            }
+            view.setAlpha(0f);
+        }
+    }
+
+    protected void showViews(ArrayList<View> views, boolean setTransitionAlpha) {
+        int count = views.size();
+        for (int i = 0; i < count; i++) {
+            showView(views.get(i), setTransitionAlpha);
+        }
+    }
+
+    private void showView(View view, boolean setTransitionAlpha) {
+        Float alpha = mOriginalAlphas.remove(view);
+        if (alpha != null) {
+            view.setAlpha(alpha);
+        }
+        if (setTransitionAlpha) {
+            view.setTransitionAlpha(1f);
+        }
+    }
+
+    /**
+     * Captures placement information for Views with a shared element name for
+     * Activity Transitions.
+     *
+     * @param view           The View to capture the placement information for.
+     * @param name           The shared element name in the target Activity to apply the placement
+     *                       information for.
+     * @param transitionArgs Bundle to store shared element placement information.
+     * @param tempBounds     A temporary Rect for capturing the current location of views.
+     */
+    protected void captureSharedElementState(View view, String name, Bundle transitionArgs,
+            Matrix tempMatrix, RectF tempBounds) {
+        Bundle sharedElementBundle = new Bundle();
+        tempMatrix.reset();
+        view.transformMatrixToGlobal(tempMatrix);
+        tempBounds.set(0, 0, view.getWidth(), view.getHeight());
+        tempMatrix.mapRect(tempBounds);
+
+        sharedElementBundle.putFloat(KEY_SCREEN_LEFT, tempBounds.left);
+        sharedElementBundle.putFloat(KEY_SCREEN_RIGHT, tempBounds.right);
+        sharedElementBundle.putFloat(KEY_SCREEN_TOP, tempBounds.top);
+        sharedElementBundle.putFloat(KEY_SCREEN_BOTTOM, tempBounds.bottom);
+        sharedElementBundle.putFloat(KEY_TRANSLATION_Z, view.getTranslationZ());
+        sharedElementBundle.putFloat(KEY_ELEVATION, view.getElevation());
+
+        Parcelable bitmap = null;
+        if (mListener != null) {
+            bitmap = mListener.onCaptureSharedElementSnapshot(view, tempMatrix, tempBounds);
+        }
+
+        if (bitmap != null) {
+            sharedElementBundle.putParcelable(KEY_SNAPSHOT, bitmap);
+        }
+
+        if (view instanceof ImageView) {
+            ImageView imageView = (ImageView) view;
+            int scaleTypeInt = scaleTypeToInt(imageView.getScaleType());
+            sharedElementBundle.putInt(KEY_SCALE_TYPE, scaleTypeInt);
+            if (imageView.getScaleType() == ImageView.ScaleType.MATRIX) {
+                float[] matrix = new float[9];
+                imageView.getImageMatrix().getValues(matrix);
+                sharedElementBundle.putFloatArray(KEY_IMAGE_MATRIX, matrix);
+            }
+        }
+
+        transitionArgs.putBundle(name, sharedElementBundle);
+    }
+
+
+    protected void startTransition(Runnable runnable) {
+        if (mIsStartingTransition) {
+            mPendingTransition = runnable;
+        } else {
+            mIsStartingTransition = true;
+            runnable.run();
+        }
+    }
+
+    protected void transitionStarted() {
+        mIsStartingTransition = false;
+    }
+
+    /**
+     * Cancels any pending transitions and returns true if there is a transition is in
+     * the middle of starting.
+     */
+    protected boolean cancelPendingTransitions() {
+        mPendingTransition = null;
+        return mIsStartingTransition;
+    }
+
+    protected void moveSharedElementsToOverlay() {
+        if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
+            return;
+        }
+        setSharedElementMatrices();
+        int numSharedElements = mSharedElements.size();
+        ViewGroup decor = getDecor();
+        if (decor != null) {
+            boolean moveWithParent = moveSharedElementWithParent();
+            Matrix tempMatrix = new Matrix();
+            for (int i = 0; i < numSharedElements; i++) {
+                View view = mSharedElements.get(i);
+                if (view.isAttachedToWindow()) {
+                    tempMatrix.reset();
+                    mSharedElementParentMatrices.get(i).invert(tempMatrix);
+                    GhostView.addGhost(view, decor, tempMatrix);
+                    ViewGroup parent = (ViewGroup) view.getParent();
+                    if (moveWithParent && !isInTransitionGroup(parent, decor)) {
+                        GhostViewListeners listener = new GhostViewListeners(view, parent, decor);
+                        parent.getViewTreeObserver().addOnPreDrawListener(listener);
+                        parent.addOnAttachStateChangeListener(listener);
+                        mGhostViewListeners.add(listener);
+                    }
+                }
+            }
+        }
+    }
+
+    protected boolean moveSharedElementWithParent() {
+        return true;
+    }
+
+    public static boolean isInTransitionGroup(ViewParent viewParent, ViewGroup decor) {
+        if (viewParent == decor || !(viewParent instanceof ViewGroup)) {
+            return false;
+        }
+        ViewGroup parent = (ViewGroup) viewParent;
+        if (parent.isTransitionGroup()) {
+            return true;
+        } else {
+            return isInTransitionGroup(parent.getParent(), decor);
+        }
+    }
+
+    protected void moveSharedElementsFromOverlay() {
+        int numListeners = mGhostViewListeners.size();
+        for (int i = 0; i < numListeners; i++) {
+            GhostViewListeners listener = mGhostViewListeners.get(i);
+            listener.removeListener();
+        }
+        mGhostViewListeners.clear();
+
+        if (mWindow == null || !mWindow.getSharedElementsUseOverlay()) {
+            return;
+        }
+        ViewGroup decor = getDecor();
+        if (decor != null) {
+            ViewGroupOverlay overlay = decor.getOverlay();
+            int count = mSharedElements.size();
+            for (int i = 0; i < count; i++) {
+                View sharedElement = mSharedElements.get(i);
+                GhostView.removeGhost(sharedElement);
+            }
+        }
+    }
+
+    protected void setGhostVisibility(int visibility) {
+        int numSharedElements = mSharedElements.size();
+        for (int i = 0; i < numSharedElements; i++) {
+            GhostView ghostView = GhostView.getGhost(mSharedElements.get(i));
+            if (ghostView != null) {
+                ghostView.setVisibility(visibility);
+            }
+        }
+    }
+
+    protected void scheduleGhostVisibilityChange(final int visibility) {
+        final View decorView = getDecor();
+        if (decorView != null) {
+            OneShotPreDrawListener.add(decorView, () -> {
+                setGhostVisibility(visibility);
+            });
+        }
+    }
+
+    protected boolean isViewsTransitionComplete() {
+        return mViewsTransitionComplete;
+    }
+
+    protected void viewsTransitionComplete() {
+        mViewsTransitionComplete = true;
+        startInputWhenTransitionsComplete();
+    }
+
+    protected void backgroundAnimatorComplete() {
+        mBackgroundAnimatorComplete = true;
+    }
+
+    protected void sharedElementTransitionComplete() {
+        mSharedElementTransitionComplete = true;
+        startInputWhenTransitionsComplete();
+    }
+    private void startInputWhenTransitionsComplete() {
+        if (mViewsTransitionComplete && mSharedElementTransitionComplete) {
+            final View decor = getDecor();
+            if (decor != null) {
+                final ViewRootImpl viewRoot = decor.getViewRootImpl();
+                if (viewRoot != null) {
+                    viewRoot.setPausedForTransition(false);
+                }
+            }
+            onTransitionsComplete();
+        }
+    }
+
+    protected void pauseInput() {
+        final View decor = getDecor();
+        final ViewRootImpl viewRoot = decor == null ? null : decor.getViewRootImpl();
+        if (viewRoot != null) {
+            viewRoot.setPausedForTransition(true);
+        }
+    }
+
+    protected void onTransitionsComplete() {}
+
+    protected class ContinueTransitionListener extends TransitionListenerAdapter {
+        @Override
+        public void onTransitionStart(Transition transition) {
+            mIsStartingTransition = false;
+            Runnable pending = mPendingTransition;
+            mPendingTransition = null;
+            if (pending != null) {
+                startTransition(pending);
+            }
+        }
+
+        @Override
+        public void onTransitionEnd(Transition transition) {
+            transition.removeListener(this);
+        }
+    }
+
+    private static int scaleTypeToInt(ImageView.ScaleType scaleType) {
+        for (int i = 0; i < SCALE_TYPE_VALUES.length; i++) {
+            if (scaleType == SCALE_TYPE_VALUES[i]) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
+    protected void setTransitioningViewsVisiblity(int visiblity, boolean invalidate) {
+        final int numElements = mTransitioningViews == null ? 0 : mTransitioningViews.size();
+        for (int i = 0; i < numElements; i++) {
+            final View view = mTransitioningViews.get(i);
+            if (invalidate) {
+                // Allow the view to be invalidated by the visibility change
+                view.setVisibility(visiblity);
+            } else {
+                // Don't invalidate the view with the visibility change
+                view.setTransitionVisibility(visiblity);
+            }
+        }
+    }
+
+    /**
+     * Blocks suppressLayout from Visibility transitions. It is ok to suppress the layout,
+     * but we don't want to force the layout when suppressLayout becomes false. This leads
+     * to visual glitches.
+     */
+    private static void noLayoutSuppressionForVisibilityTransitions(Transition transition) {
+        if (transition instanceof Visibility) {
+            final Visibility visibility = (Visibility) transition;
+            visibility.setSuppressLayout(false);
+        } else if (transition instanceof TransitionSet) {
+            final TransitionSet set = (TransitionSet) transition;
+            final int count = set.getTransitionCount();
+            for (int i = 0; i < count; i++) {
+                noLayoutSuppressionForVisibilityTransitions(set.getTransitionAt(i));
+            }
+        }
+    }
+
+    public boolean isTransitionRunning() {
+        return !(mViewsTransitionComplete && mSharedElementTransitionComplete &&
+                mBackgroundAnimatorComplete);
+    }
+
+    private static class FixedEpicenterCallback extends Transition.EpicenterCallback {
+        private Rect mEpicenter;
+
+        public void setEpicenter(Rect epicenter) { mEpicenter = epicenter; }
+
+        @Override
+        public Rect onGetEpicenter(Transition transition) {
+            return mEpicenter;
+        }
+    }
+
+    private static class GhostViewListeners implements ViewTreeObserver.OnPreDrawListener,
+            View.OnAttachStateChangeListener {
+        private View mView;
+        private ViewGroup mDecor;
+        private View mParent;
+        private Matrix mMatrix = new Matrix();
+        private ViewTreeObserver mViewTreeObserver;
+
+        public GhostViewListeners(View view, View parent, ViewGroup decor) {
+            mView = view;
+            mParent = parent;
+            mDecor = decor;
+            mViewTreeObserver = parent.getViewTreeObserver();
+        }
+
+        public View getView() {
+            return mView;
+        }
+
+        @Override
+        public boolean onPreDraw() {
+            GhostView ghostView = GhostView.getGhost(mView);
+            if (ghostView == null || !mView.isAttachedToWindow()) {
+                removeListener();
+            } else {
+                GhostView.calculateMatrix(mView, mDecor, mMatrix);
+                ghostView.setMatrix(mMatrix);
+            }
+            return true;
+        }
+
+        public void removeListener() {
+            if (mViewTreeObserver.isAlive()) {
+                mViewTreeObserver.removeOnPreDrawListener(this);
+            } else {
+                mParent.getViewTreeObserver().removeOnPreDrawListener(this);
+            }
+            mParent.removeOnAttachStateChangeListener(this);
+        }
+
+        @Override
+        public void onViewAttachedToWindow(View v) {
+            mViewTreeObserver = v.getViewTreeObserver();
+        }
+
+        @Override
+        public void onViewDetachedFromWindow(View v) {
+            removeListener();
+        }
+    }
+
+    static class SharedElementOriginalState {
+        int mLeft;
+        int mTop;
+        int mRight;
+        int mBottom;
+        int mMeasuredWidth;
+        int mMeasuredHeight;
+        ImageView.ScaleType mScaleType;
+        Matrix mMatrix;
+        float mTranslationZ;
+        float mElevation;
+    }
+}
diff --git a/android/app/ActivityTransitionState.java b/android/app/ActivityTransitionState.java
new file mode 100644
index 0000000..3a95839
--- /dev/null
+++ b/android/app/ActivityTransitionState.java
@@ -0,0 +1,393 @@
+/*
+ * 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.app;
+
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.ResultReceiver;
+import android.transition.Transition;
+import android.util.SparseArray;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import com.android.internal.view.OneShotPreDrawListener;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+
+/**
+ * This class contains all persistence-related functionality for Activity Transitions.
+ * Activities start exit and enter Activity Transitions through this class.
+ */
+class ActivityTransitionState {
+
+    private static final String PENDING_EXIT_SHARED_ELEMENTS = "android:pendingExitSharedElements";
+
+    private static final String EXITING_MAPPED_FROM = "android:exitingMappedFrom";
+
+    private static final String EXITING_MAPPED_TO = "android:exitingMappedTo";
+
+    /**
+     * The shared elements that the calling Activity has said that they transferred to this
+     * Activity and will be transferred back during exit animation.
+     */
+    private ArrayList<String> mPendingExitNames;
+
+    /**
+     * The names of shared elements that were shared to the called Activity.
+     */
+    private ArrayList<String> mExitingFrom;
+
+    /**
+     * The names of local Views that were shared out, mapped to those elements in mExitingFrom.
+     */
+    private ArrayList<String> mExitingTo;
+
+    /**
+     * The local Views that were shared out, mapped to those elements in mExitingFrom.
+     */
+    private ArrayList<View> mExitingToView;
+
+    /**
+     * The ExitTransitionCoordinator used to start an Activity. Used to make the elements restore
+     * Visibility of exited Views.
+     */
+    private ExitTransitionCoordinator mCalledExitCoordinator;
+
+    /**
+     * The ExitTransitionCoordinator used to return to a previous Activity when called with
+     * {@link android.app.Activity#finishAfterTransition()}.
+     */
+    private ExitTransitionCoordinator mReturnExitCoordinator;
+
+    /**
+     * We must be able to cancel entering transitions to stop changing the Window to
+     * opaque when we exit before making the Window opaque.
+     */
+    private EnterTransitionCoordinator mEnterTransitionCoordinator;
+
+    /**
+     * ActivityOptions used on entering this Activity.
+     */
+    private ActivityOptions mEnterActivityOptions;
+
+    /**
+     * Has an exit transition been started? If so, we don't want to double-exit.
+     */
+    private boolean mHasExited;
+
+    /**
+     * Postpone painting and starting the enter transition until this is false.
+     */
+    private boolean mIsEnterPostponed;
+
+    /**
+     * Potential exit transition coordinators.
+     */
+    private SparseArray<WeakReference<ExitTransitionCoordinator>> mExitTransitionCoordinators;
+
+    /**
+     * Next key for mExitTransitionCoordinator.
+     */
+    private int mExitTransitionCoordinatorsKey = 1;
+
+    private boolean mIsEnterTriggered;
+
+    public ActivityTransitionState() {
+    }
+
+    public int addExitTransitionCoordinator(ExitTransitionCoordinator exitTransitionCoordinator) {
+        if (mExitTransitionCoordinators == null) {
+            mExitTransitionCoordinators = new SparseArray<>();
+        }
+        WeakReference<ExitTransitionCoordinator> ref = new WeakReference(exitTransitionCoordinator);
+        // clean up old references:
+        for (int i = mExitTransitionCoordinators.size() - 1; i >= 0; i--) {
+            WeakReference<ExitTransitionCoordinator> oldRef
+                    = mExitTransitionCoordinators.valueAt(i);
+            if (oldRef.get() == null) {
+                mExitTransitionCoordinators.removeAt(i);
+            }
+        }
+        int newKey = mExitTransitionCoordinatorsKey++;
+        mExitTransitionCoordinators.append(newKey, ref);
+        return newKey;
+    }
+
+    public void readState(Bundle bundle) {
+        if (bundle != null) {
+            if (mEnterTransitionCoordinator == null || mEnterTransitionCoordinator.isReturning()) {
+                mPendingExitNames = bundle.getStringArrayList(PENDING_EXIT_SHARED_ELEMENTS);
+            }
+            if (mEnterTransitionCoordinator == null) {
+                mExitingFrom = bundle.getStringArrayList(EXITING_MAPPED_FROM);
+                mExitingTo = bundle.getStringArrayList(EXITING_MAPPED_TO);
+            }
+        }
+    }
+
+    /**
+     * Returns the element names to be used for exit animation. It caches the list internally so
+     * that it is preserved through activty destroy and restore.
+     */
+    private ArrayList<String> getPendingExitNames() {
+        if (mPendingExitNames == null && mEnterTransitionCoordinator != null) {
+            mPendingExitNames = mEnterTransitionCoordinator.getPendingExitSharedElementNames();
+        }
+        return mPendingExitNames;
+    }
+
+    public void saveState(Bundle bundle) {
+        ArrayList<String> pendingExitNames = getPendingExitNames();
+        if (pendingExitNames != null) {
+            bundle.putStringArrayList(PENDING_EXIT_SHARED_ELEMENTS, pendingExitNames);
+        }
+        if (mExitingFrom != null) {
+            bundle.putStringArrayList(EXITING_MAPPED_FROM, mExitingFrom);
+            bundle.putStringArrayList(EXITING_MAPPED_TO, mExitingTo);
+        }
+    }
+
+    public void setEnterActivityOptions(Activity activity, ActivityOptions options) {
+        final Window window = activity.getWindow();
+        if (window == null) {
+            return;
+        }
+        // ensure Decor View has been created so that the window features are activated
+        window.getDecorView();
+        if (window.hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS)
+                && options != null && mEnterActivityOptions == null
+                && mEnterTransitionCoordinator == null
+                && options.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+            mEnterActivityOptions = options;
+            mIsEnterTriggered = false;
+            if (mEnterActivityOptions.isReturning()) {
+                restoreExitedViews();
+                int result = mEnterActivityOptions.getResultCode();
+                if (result != 0) {
+                    Intent intent = mEnterActivityOptions.getResultData();
+                    if (intent != null) {
+                        intent.setExtrasClassLoader(activity.getClassLoader());
+                    }
+                    activity.onActivityReenter(result, intent);
+                }
+            }
+        }
+    }
+
+    public void enterReady(Activity activity) {
+        if (mEnterActivityOptions == null || mIsEnterTriggered) {
+            return;
+        }
+        mIsEnterTriggered = true;
+        mHasExited = false;
+        ArrayList<String> sharedElementNames = mEnterActivityOptions.getSharedElementNames();
+        ResultReceiver resultReceiver = mEnterActivityOptions.getResultReceiver();
+        if (mEnterActivityOptions.isReturning()) {
+            restoreExitedViews();
+            activity.getWindow().getDecorView().setVisibility(View.VISIBLE);
+        }
+        mEnterTransitionCoordinator = new EnterTransitionCoordinator(activity,
+                resultReceiver, sharedElementNames, mEnterActivityOptions.isReturning(),
+                mEnterActivityOptions.isCrossTask());
+        if (mEnterActivityOptions.isCrossTask()) {
+            mExitingFrom = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+            mExitingTo = new ArrayList<>(mEnterActivityOptions.getSharedElementNames());
+        }
+
+        if (!mIsEnterPostponed) {
+            startEnter();
+        }
+    }
+
+    public void postponeEnterTransition() {
+        mIsEnterPostponed = true;
+    }
+
+    public void startPostponedEnterTransition() {
+        if (mIsEnterPostponed) {
+            mIsEnterPostponed = false;
+            if (mEnterTransitionCoordinator != null) {
+                startEnter();
+            }
+        }
+    }
+
+    private void startEnter() {
+        if (mEnterTransitionCoordinator.isReturning()) {
+            if (mExitingToView != null) {
+                mEnterTransitionCoordinator.viewInstancesReady(mExitingFrom, mExitingTo,
+                        mExitingToView);
+            } else {
+                mEnterTransitionCoordinator.namedViewsReady(mExitingFrom, mExitingTo);
+            }
+        } else {
+            mEnterTransitionCoordinator.namedViewsReady(null, null);
+            mPendingExitNames = null;
+        }
+
+        mExitingFrom = null;
+        mExitingTo = null;
+        mExitingToView = null;
+        mEnterActivityOptions = null;
+    }
+
+    public void onStop() {
+        restoreExitedViews();
+        if (mEnterTransitionCoordinator != null) {
+            mEnterTransitionCoordinator.stop();
+            mEnterTransitionCoordinator = null;
+        }
+        if (mReturnExitCoordinator != null) {
+            mReturnExitCoordinator.stop();
+            mReturnExitCoordinator = null;
+        }
+    }
+
+    public void onResume(Activity activity) {
+        // After orientation change, the onResume can come in before the top Activity has
+        // left, so if the Activity is not top, wait a second for the top Activity to exit.
+        if (mEnterTransitionCoordinator == null || activity.isTopOfTask()) {
+            restoreExitedViews();
+            restoreReenteringViews();
+        } else {
+            activity.mHandler.postDelayed(new Runnable() {
+                @Override
+                public void run() {
+                    if (mEnterTransitionCoordinator == null ||
+                            mEnterTransitionCoordinator.isWaitingForRemoteExit()) {
+                        restoreExitedViews();
+                        restoreReenteringViews();
+                    }
+                }
+            }, 1000);
+        }
+    }
+
+    public void clear() {
+        mPendingExitNames = null;
+        mExitingFrom = null;
+        mExitingTo = null;
+        mExitingToView = null;
+        mCalledExitCoordinator = null;
+        mEnterTransitionCoordinator = null;
+        mEnterActivityOptions = null;
+        mExitTransitionCoordinators = null;
+    }
+
+    private void restoreExitedViews() {
+        if (mCalledExitCoordinator != null) {
+            mCalledExitCoordinator.resetViews();
+            mCalledExitCoordinator = null;
+        }
+    }
+
+    private void restoreReenteringViews() {
+        if (mEnterTransitionCoordinator != null && mEnterTransitionCoordinator.isReturning() &&
+                !mEnterTransitionCoordinator.isCrossTask()) {
+            mEnterTransitionCoordinator.forceViewsToAppear();
+            mExitingFrom = null;
+            mExitingTo = null;
+            mExitingToView = null;
+        }
+    }
+
+    public boolean startExitBackTransition(final Activity activity) {
+        ArrayList<String> pendingExitNames = getPendingExitNames();
+        if (pendingExitNames == null || mCalledExitCoordinator != null) {
+            return false;
+        } else {
+            if (!mHasExited) {
+                mHasExited = true;
+                Transition enterViewsTransition = null;
+                ViewGroup decor = null;
+                boolean delayExitBack = false;
+                if (mEnterTransitionCoordinator != null) {
+                    enterViewsTransition = mEnterTransitionCoordinator.getEnterViewsTransition();
+                    decor = mEnterTransitionCoordinator.getDecor();
+                    delayExitBack = mEnterTransitionCoordinator.cancelEnter();
+                    mEnterTransitionCoordinator = null;
+                    if (enterViewsTransition != null && decor != null) {
+                        enterViewsTransition.pause(decor);
+                    }
+                }
+
+                mReturnExitCoordinator = new ExitTransitionCoordinator(activity,
+                        activity.getWindow(), activity.mEnterTransitionListener, pendingExitNames,
+                        null, null, true);
+                if (enterViewsTransition != null && decor != null) {
+                    enterViewsTransition.resume(decor);
+                }
+                if (delayExitBack && decor != null) {
+                    final ViewGroup finalDecor = decor;
+                    OneShotPreDrawListener.add(decor, () -> {
+                        if (mReturnExitCoordinator != null) {
+                            mReturnExitCoordinator.startExit(activity.mResultCode,
+                                    activity.mResultData);
+                        }
+                    });
+                } else {
+                    mReturnExitCoordinator.startExit(activity.mResultCode, activity.mResultData);
+                }
+            }
+            return true;
+        }
+    }
+
+    public boolean isTransitionRunning() {
+        // Note that *only* enter *or* exit will be running at any given time
+        if (mEnterTransitionCoordinator != null) {
+            if (mEnterTransitionCoordinator.isTransitionRunning()) {
+                return true;
+            }
+        }
+        if (mCalledExitCoordinator != null) {
+            if (mCalledExitCoordinator.isTransitionRunning()) {
+                return true;
+            }
+        }
+        if (mReturnExitCoordinator != null) {
+            if (mReturnExitCoordinator.isTransitionRunning()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public void startExitOutTransition(Activity activity, Bundle options) {
+        mEnterTransitionCoordinator = null;
+        if (!activity.getWindow().hasFeature(Window.FEATURE_ACTIVITY_TRANSITIONS) ||
+                mExitTransitionCoordinators == null) {
+            return;
+        }
+        ActivityOptions activityOptions = new ActivityOptions(options);
+        if (activityOptions.getAnimationType() == ActivityOptions.ANIM_SCENE_TRANSITION) {
+            int key = activityOptions.getExitCoordinatorKey();
+            int index = mExitTransitionCoordinators.indexOfKey(key);
+            if (index >= 0) {
+                mCalledExitCoordinator = mExitTransitionCoordinators.valueAt(index).get();
+                mExitTransitionCoordinators.removeAt(index);
+                if (mCalledExitCoordinator != null) {
+                    mExitingFrom = mCalledExitCoordinator.getAcceptedNames();
+                    mExitingTo = mCalledExitCoordinator.getMappedNames();
+                    mExitingToView = mCalledExitCoordinator.copyMappedViews();
+                    mCalledExitCoordinator.startExit();
+                }
+            }
+        }
+    }
+}
diff --git a/android/app/ActivityView.java b/android/app/ActivityView.java
new file mode 100644
index 0000000..4771f9f
--- /dev/null
+++ b/android/app/ActivityView.java
@@ -0,0 +1,718 @@
+/**
+ * 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.app;
+
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
+import static android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
+import static android.view.Display.INVALID_DISPLAY;
+
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.app.ActivityManager.StackInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Insets;
+import android.graphics.Matrix;
+import android.graphics.Region;
+import android.hardware.display.DisplayManager;
+import android.hardware.display.VirtualDisplay;
+import android.hardware.input.InputManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.UserHandle;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.Log;
+import android.view.IWindowManager;
+import android.view.InputDevice;
+import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
+import android.view.SurfaceControl;
+import android.view.SurfaceHolder;
+import android.view.SurfaceSession;
+import android.view.SurfaceView;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.WindowManager;
+import android.view.WindowManagerGlobal;
+import android.view.inputmethod.InputMethodManager;
+
+import dalvik.system.CloseGuard;
+
+import java.util.List;
+
+/**
+ * Activity container that allows launching activities into itself.
+ * <p>Activity launching into this container is restricted by the same rules that apply to launching
+ * on VirtualDisplays.
+ * @hide
+ */
+@TestApi
+public class ActivityView extends ViewGroup {
+
+    private static final String DISPLAY_NAME = "ActivityViewVirtualDisplay";
+    private static final String TAG = "ActivityView";
+
+    private VirtualDisplay mVirtualDisplay;
+    private final SurfaceView mSurfaceView;
+
+    /**
+     * This is the root surface for the VirtualDisplay. The VirtualDisplay child surfaces will be
+     * re-parented to this surface. This will also be a child of the SurfaceView's SurfaceControl.
+     */
+    private SurfaceControl mRootSurfaceControl;
+
+    private final SurfaceCallback mSurfaceCallback;
+    private StateCallback mActivityViewCallback;
+
+    private IActivityTaskManager mActivityTaskManager;
+    // Temp container to store view coordinates in window.
+    private final int[] mLocationInWindow = new int[2];
+
+    // The latest tap exclude region that we've sent to WM.
+    private final Region mTapExcludeRegion = new Region();
+
+    private TaskStackListener mTaskStackListener;
+
+    private final CloseGuard mGuard = CloseGuard.get();
+    private boolean mOpened; // Protected by mGuard.
+
+    private final SurfaceControl.Transaction mTmpTransaction = new SurfaceControl.Transaction();
+
+    /** The ActivityView is only allowed to contain one task. */
+    private final boolean mSingleTaskInstance;
+
+    private Insets mForwardedInsets;
+
+    public ActivityView(Context context) {
+        this(context, null /* attrs */);
+    }
+
+    public ActivityView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0 /* defStyle */);
+    }
+
+    public ActivityView(Context context, AttributeSet attrs, int defStyle) {
+        this(context, attrs, defStyle, false /*singleTaskInstance*/);
+    }
+
+    public ActivityView(
+            Context context, AttributeSet attrs, int defStyle, boolean singleTaskInstance) {
+        super(context, attrs, defStyle);
+        mSingleTaskInstance = singleTaskInstance;
+
+        mActivityTaskManager = ActivityTaskManager.getService();
+        mSurfaceView = new SurfaceView(context);
+        mSurfaceCallback = new SurfaceCallback();
+        mSurfaceView.getHolder().addCallback(mSurfaceCallback);
+        addView(mSurfaceView);
+
+        mOpened = true;
+        mGuard.open("release");
+    }
+
+    /** Callback that notifies when the container is ready or destroyed. */
+    public abstract static class StateCallback {
+
+        /**
+         * Called when the container is ready for launching activities. Calling
+         * {@link #startActivity(Intent)} prior to this callback will result in an
+         * {@link IllegalStateException}.
+         *
+         * @see #startActivity(Intent)
+         */
+        public abstract void onActivityViewReady(ActivityView view);
+
+        /**
+         * Called when the container can no longer launch activities. Calling
+         * {@link #startActivity(Intent)} after this callback will result in an
+         * {@link IllegalStateException}.
+         *
+         * @see #startActivity(Intent)
+         */
+        public abstract void onActivityViewDestroyed(ActivityView view);
+
+        /**
+         * Called when a task is created inside the container.
+         * This is a filtered version of {@link TaskStackListener}
+         */
+        public void onTaskCreated(int taskId, ComponentName componentName) { }
+
+        /**
+         * Called when a task is moved to the front of the stack inside the container.
+         * This is a filtered version of {@link TaskStackListener}
+         */
+        public void onTaskMovedToFront(int taskId) { }
+
+        /**
+         * Called when a task is about to be removed from the stack inside the container.
+         * This is a filtered version of {@link TaskStackListener}
+         */
+        public void onTaskRemovalStarted(int taskId) { }
+    }
+
+    /**
+     * Set the callback to be notified about state changes.
+     * <p>This class must finish initializing before {@link #startActivity(Intent)} can be called.
+     * <p>Note: If the instance was ready prior to this call being made, then
+     * {@link StateCallback#onActivityViewReady(ActivityView)} will be called from within
+     * this method call.
+     *
+     * @param callback The callback to report events to.
+     *
+     * @see StateCallback
+     * @see #startActivity(Intent)
+     */
+    public void setCallback(StateCallback callback) {
+        mActivityViewCallback = callback;
+
+        if (mVirtualDisplay != null && mActivityViewCallback != null) {
+            mActivityViewCallback.onActivityViewReady(this);
+        }
+    }
+
+    /**
+     * Sets the corner radius for the Activity displayed here. The corners will be
+     * cropped from the window painted by the contained Activity.
+     *
+     * @param cornerRadius the radius for the corners, in pixels
+     * @hide
+     */
+    public void setCornerRadius(float cornerRadius) {
+        mSurfaceView.setCornerRadius(cornerRadius);
+    }
+
+    /**
+     * Launch a new activity into this container.
+     * <p>Activity resolved by the provided {@link Intent} must have
+     * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
+     * launched here. Also, if activity is not owned by the owner of this container, it must allow
+     * embedding and the caller must have permission to embed.
+     * <p>Note: This class must finish initializing and
+     * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
+     * this method can be called.
+     *
+     * @param intent Intent used to launch an activity.
+     *
+     * @see StateCallback
+     * @see #startActivity(PendingIntent)
+     */
+    public void startActivity(@NonNull Intent intent) {
+        final ActivityOptions options = prepareActivityOptions();
+        getContext().startActivity(intent, options.toBundle());
+    }
+
+    /**
+     * Launch a new activity into this container.
+     * <p>Activity resolved by the provided {@link Intent} must have
+     * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
+     * launched here. Also, if activity is not owned by the owner of this container, it must allow
+     * embedding and the caller must have permission to embed.
+     * <p>Note: This class must finish initializing and
+     * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
+     * this method can be called.
+     *
+     * @param intent Intent used to launch an activity.
+     * @param user The UserHandle of the user to start this activity for.
+     *
+     *
+     * @see StateCallback
+     * @see #startActivity(PendingIntent)
+     */
+    public void startActivity(@NonNull Intent intent, UserHandle user) {
+        final ActivityOptions options = prepareActivityOptions();
+        getContext().startActivityAsUser(intent, options.toBundle(), user);
+    }
+
+    /**
+     * Launch a new activity into this container.
+     * <p>Activity resolved by the provided {@link PendingIntent} must have
+     * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
+     * launched here. Also, if activity is not owned by the owner of this container, it must allow
+     * embedding and the caller must have permission to embed.
+     * <p>Note: This class must finish initializing and
+     * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
+     * this method can be called.
+     *
+     * @param pendingIntent Intent used to launch an activity.
+     *
+     * @see StateCallback
+     * @see #startActivity(Intent)
+     */
+    public void startActivity(@NonNull PendingIntent pendingIntent) {
+        final ActivityOptions options = prepareActivityOptions();
+        try {
+            pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
+                    null /* onFinished */, null /* handler */, null /* requiredPermission */,
+                    options.toBundle());
+        } catch (PendingIntent.CanceledException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Launch a new activity into this container.
+     * <p>Activity resolved by the provided {@link PendingIntent} must have
+     * {@link android.R.attr#resizeableActivity} attribute set to {@code true} in order to be
+     * launched here. Also, if activity is not owned by the owner of this container, it must allow
+     * embedding and the caller must have permission to embed.
+     * <p>Note: This class must finish initializing and
+     * {@link StateCallback#onActivityViewReady(ActivityView)} callback must be triggered before
+     * this method can be called.
+     *
+     * @param pendingIntent Intent used to launch an activity.
+     * @param options options for the activity
+     *
+     * @see StateCallback
+     * @see #startActivity(Intent)
+     */
+    public void startActivity(@NonNull PendingIntent pendingIntent,
+            @NonNull ActivityOptions options) {
+        options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
+        try {
+            pendingIntent.send(null /* context */, 0 /* code */, null /* intent */,
+                    null /* onFinished */, null /* handler */, null /* requiredPermission */,
+                    options.toBundle());
+        } catch (PendingIntent.CanceledException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Check if container is ready to launch and create {@link ActivityOptions} to target the
+     * virtual display.
+     */
+    private ActivityOptions prepareActivityOptions() {
+        if (mVirtualDisplay == null) {
+            throw new IllegalStateException(
+                    "Trying to start activity before ActivityView is ready.");
+        }
+        final ActivityOptions options = ActivityOptions.makeBasic();
+        options.setLaunchDisplayId(mVirtualDisplay.getDisplay().getDisplayId());
+        return options;
+    }
+
+    /**
+     * Release this container. Activity launching will no longer be permitted.
+     * <p>Note: Calling this method is allowed after
+     * {@link StateCallback#onActivityViewReady(ActivityView)} callback was triggered and before
+     * {@link StateCallback#onActivityViewDestroyed(ActivityView)}.
+     *
+     * @see StateCallback
+     */
+    public void release() {
+        if (mVirtualDisplay == null) {
+            throw new IllegalStateException(
+                    "Trying to release container that is not initialized.");
+        }
+        performRelease();
+    }
+
+    /**
+     * Triggers an update of {@link ActivityView}'s location in window to properly set tap exclude
+     * regions and avoid focus switches by touches on this view.
+     */
+    public void onLocationChanged() {
+        updateLocationAndTapExcludeRegion();
+    }
+
+    private void clearActivityViewGeometryForIme() {
+        if (mVirtualDisplay == null) {
+            return;
+        }
+        final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
+        mContext.getSystemService(InputMethodManager.class).reportActivityView(displayId, null);
+    }
+
+    @Override
+    public void onLayout(boolean changed, int l, int t, int r, int b) {
+        mSurfaceView.layout(0 /* left */, 0 /* top */, r - l /* right */, b - t /* bottom */);
+    }
+
+    @Override
+    public boolean gatherTransparentRegion(Region region) {
+        // The tap exclude region may be affected by any view on top of it, so we detect the
+        // possible change by monitoring this function.
+        updateLocationAndTapExcludeRegion();
+        return super.gatherTransparentRegion(region);
+    }
+
+    /**
+     * Sends current location in window and tap exclude region to WM for this view.
+     */
+    private void updateLocationAndTapExcludeRegion() {
+        if (mVirtualDisplay == null || !isAttachedToWindow()) {
+            return;
+        }
+        try {
+            int x = mLocationInWindow[0];
+            int y = mLocationInWindow[1];
+            getLocationInWindow(mLocationInWindow);
+            if (x != mLocationInWindow[0] || y != mLocationInWindow[1]) {
+                x = mLocationInWindow[0];
+                y = mLocationInWindow[1];
+                final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
+                WindowManagerGlobal.getWindowSession().updateDisplayContentLocation(
+                        getWindow(), x, y, displayId);
+
+                // Also report this geometry information to InputMethodManagerService.
+                // TODO(b/115693908): Unify this logic into the above WMS-based one.
+                final Matrix matrix = new Matrix();
+                matrix.set(getMatrix());
+                matrix.postTranslate(x, y);
+                mContext.getSystemService(InputMethodManager.class)
+                        .reportActivityView(displayId, matrix);
+            }
+            updateTapExcludeRegion(x, y);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** Computes and sends current tap exclude region to WM for this view. */
+    private void updateTapExcludeRegion(int x, int y) throws RemoteException {
+        if (!canReceivePointerEvents()) {
+            cleanTapExcludeRegion();
+            return;
+        }
+        mTapExcludeRegion.set(x, y, x + getWidth(), y + getHeight());
+
+        // There might be views on top of us. We need to subtract those areas from the tap
+        // exclude region.
+        final ViewParent parent = getParent();
+        if (parent != null) {
+            parent.subtractObscuredTouchableRegion(mTapExcludeRegion, this);
+        }
+
+        WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
+                mTapExcludeRegion);
+    }
+
+    private class SurfaceCallback implements SurfaceHolder.Callback {
+        @Override
+        public void surfaceCreated(SurfaceHolder surfaceHolder) {
+            if (mVirtualDisplay == null) {
+                initVirtualDisplay(new SurfaceSession());
+                if (mVirtualDisplay != null && mActivityViewCallback != null) {
+                    mActivityViewCallback.onActivityViewReady(ActivityView.this);
+                }
+            } else {
+                mTmpTransaction.reparent(mRootSurfaceControl,
+                        mSurfaceView.getSurfaceControl()).apply();
+            }
+
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.setDisplayState(true);
+            }
+
+            updateLocationAndTapExcludeRegion();
+        }
+
+        @Override
+        public void surfaceChanged(SurfaceHolder surfaceHolder, int format, int width, int height) {
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.resize(width, height, getBaseDisplayDensity());
+            }
+            updateLocationAndTapExcludeRegion();
+        }
+
+        @Override
+        public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
+            if (mVirtualDisplay != null) {
+                mVirtualDisplay.setDisplayState(false);
+            }
+            clearActivityViewGeometryForIme();
+            cleanTapExcludeRegion();
+        }
+    }
+
+    @Override
+    protected void onVisibilityChanged(View changedView, int visibility) {
+        super.onVisibilityChanged(changedView, visibility);
+        mSurfaceView.setVisibility(visibility);
+    }
+
+    /**
+     * @return the display id of the virtual display.
+     */
+    public int getVirtualDisplayId() {
+        if (mVirtualDisplay != null) {
+            return mVirtualDisplay.getDisplay().getDisplayId();
+        }
+        return INVALID_DISPLAY;
+    }
+
+    /**
+     * Injects a pair of down/up key events with keycode {@link KeyEvent#KEYCODE_BACK} to the
+     * virtual display.
+     */
+    public void performBackPress() {
+        if (mVirtualDisplay == null) {
+            return;
+        }
+        final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
+        final InputManager im = InputManager.getInstance();
+        im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, displayId),
+                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+        im.injectInputEvent(createKeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK, displayId),
+                InputManager.INJECT_INPUT_EVENT_MODE_ASYNC);
+    }
+
+    private static KeyEvent createKeyEvent(int action, int code, int displayId) {
+        long when = SystemClock.uptimeMillis();
+        final KeyEvent ev = new KeyEvent(when, when, action, code, 0 /* repeat */,
+                0 /* metaState */, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
+                KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
+                InputDevice.SOURCE_KEYBOARD);
+        ev.setDisplayId(displayId);
+        return ev;
+    }
+
+    private void initVirtualDisplay(SurfaceSession surfaceSession) {
+        if (mVirtualDisplay != null) {
+            throw new IllegalStateException("Trying to initialize for the second time.");
+        }
+
+        final int width = mSurfaceView.getWidth();
+        final int height = mSurfaceView.getHeight();
+        final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class);
+
+        mVirtualDisplay = displayManager.createVirtualDisplay(
+                DISPLAY_NAME + "@" + System.identityHashCode(this), width, height,
+                getBaseDisplayDensity(), null,
+                VIRTUAL_DISPLAY_FLAG_PUBLIC | VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY
+                        | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL);
+        if (mVirtualDisplay == null) {
+            Log.e(TAG, "Failed to initialize ActivityView");
+            return;
+        }
+
+        final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
+        final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+
+        mRootSurfaceControl = new SurfaceControl.Builder(surfaceSession)
+                .setContainerLayer()
+                .setParent(mSurfaceView.getSurfaceControl())
+                .setName(DISPLAY_NAME)
+                .build();
+
+        try {
+            // TODO: Find a way to consolidate these calls to the server.
+            WindowManagerGlobal.getWindowSession().reparentDisplayContent(
+                    getWindow(), mRootSurfaceControl, displayId);
+            wm.dontOverrideDisplayInfo(displayId);
+            if (mSingleTaskInstance) {
+                mActivityTaskManager.setDisplayToSingleTaskInstance(displayId);
+            }
+            wm.setForwardedInsets(displayId, mForwardedInsets);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+
+        mTmpTransaction.show(mRootSurfaceControl).apply();
+        mTaskStackListener = new TaskStackListenerImpl();
+        try {
+            mActivityTaskManager.registerTaskStackListener(mTaskStackListener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register task stack listener", e);
+        }
+    }
+
+    private void performRelease() {
+        if (!mOpened) {
+            return;
+        }
+
+        mSurfaceView.getHolder().removeCallback(mSurfaceCallback);
+
+        cleanTapExcludeRegion();
+
+        if (mTaskStackListener != null) {
+            try {
+                mActivityTaskManager.unregisterTaskStackListener(mTaskStackListener);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Failed to unregister task stack listener", e);
+            }
+            mTaskStackListener = null;
+        }
+
+        final boolean displayReleased;
+        if (mVirtualDisplay != null) {
+            mVirtualDisplay.release();
+            mVirtualDisplay = null;
+            displayReleased = true;
+        } else {
+            displayReleased = false;
+        }
+
+        if (displayReleased && mActivityViewCallback != null) {
+            mActivityViewCallback.onActivityViewDestroyed(this);
+        }
+
+        mGuard.close();
+        mOpened = false;
+    }
+
+    /** Report to server that tap exclude region on hosting display should be cleared. */
+    private void cleanTapExcludeRegion() {
+        if (!isAttachedToWindow() || mTapExcludeRegion.isEmpty()) {
+            return;
+        }
+        // Update tap exclude region with a null region to clean the state on server.
+        try {
+            WindowManagerGlobal.getWindowSession().updateTapExcludeRegion(getWindow(), hashCode(),
+                    null /* region */);
+            mTapExcludeRegion.setEmpty();
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** Get density of the hosting display. */
+    private int getBaseDisplayDensity() {
+        final WindowManager wm = mContext.getSystemService(WindowManager.class);
+        final DisplayMetrics metrics = new DisplayMetrics();
+        wm.getDefaultDisplay().getMetrics(metrics);
+        return metrics.densityDpi;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            if (mGuard != null) {
+                mGuard.warnIfOpen();
+                performRelease();
+            }
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Set forwarded insets on the virtual display.
+     *
+     * @see IWindowManager#setForwardedInsets
+     */
+    public void setForwardedInsets(Insets insets) {
+        mForwardedInsets = insets;
+        if (mVirtualDisplay == null) {
+            return;
+        }
+        try {
+            final IWindowManager wm = WindowManagerGlobal.getWindowManagerService();
+            wm.setForwardedInsets(mVirtualDisplay.getDisplay().getDisplayId(), mForwardedInsets);
+        } catch (RemoteException e) {
+            e.rethrowAsRuntimeException();
+        }
+    }
+
+    /**
+     * A task change listener that detects background color change of the topmost stack on our
+     * virtual display and updates the background of the surface view. This background will be shown
+     * when surface view is resized, but the app hasn't drawn its content in new size yet.
+     * It also calls StateCallback.onTaskMovedToFront to notify interested parties that the stack
+     * associated with the {@link ActivityView} has had a Task moved to the front. This is useful
+     * when needing to also bring the host Activity to the foreground at the same time.
+     */
+    private class TaskStackListenerImpl extends TaskStackListener {
+
+        @Override
+        public void onTaskDescriptionChanged(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            if (mVirtualDisplay == null
+                    || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
+                return;
+            }
+
+            StackInfo stackInfo = getTopMostStackInfo();
+            if (stackInfo == null) {
+                return;
+            }
+            // Found the topmost stack on target display. Now check if the topmost task's
+            // description changed.
+            if (taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+                mSurfaceView.setResizeBackgroundColor(
+                        taskInfo.taskDescription.getBackgroundColor());
+            }
+        }
+
+        @Override
+        public void onTaskMovedToFront(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            if (mActivityViewCallback  == null || mVirtualDisplay == null
+                    || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
+                return;
+            }
+
+            StackInfo stackInfo = getTopMostStackInfo();
+            // if StackInfo was null or unrelated to the "move to front" then there's no use
+            // notifying the callback
+            if (stackInfo != null
+                    && taskInfo.taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+                mActivityViewCallback.onTaskMovedToFront(taskInfo.taskId);
+            }
+        }
+
+        @Override
+        public void onTaskCreated(int taskId, ComponentName componentName) throws RemoteException {
+            if (mActivityViewCallback == null || mVirtualDisplay == null) {
+                return;
+            }
+
+            StackInfo stackInfo = getTopMostStackInfo();
+            // if StackInfo was null or unrelated to the task creation then there's no use
+            // notifying the callback
+            if (stackInfo != null
+                    && taskId == stackInfo.taskIds[stackInfo.taskIds.length - 1]) {
+                mActivityViewCallback.onTaskCreated(taskId, componentName);
+            }
+        }
+
+        @Override
+        public void onTaskRemovalStarted(ActivityManager.RunningTaskInfo taskInfo)
+                throws RemoteException {
+            if (mActivityViewCallback == null || mVirtualDisplay == null
+                    || taskInfo.displayId != mVirtualDisplay.getDisplay().getDisplayId()) {
+                return;
+            }
+            mActivityViewCallback.onTaskRemovalStarted(taskInfo.taskId);
+        }
+
+        private StackInfo getTopMostStackInfo() throws RemoteException {
+            // Find the topmost task on our virtual display - it will define the background
+            // color of the surface view during resizing.
+            final int displayId = mVirtualDisplay.getDisplay().getDisplayId();
+            final List<StackInfo> stackInfoList = mActivityTaskManager.getAllStackInfos();
+
+            // Iterate through stacks from top to bottom.
+            final int stackCount = stackInfoList.size();
+            for (int i = 0; i < stackCount; i++) {
+                final StackInfo stackInfo = stackInfoList.get(i);
+                // Only look for stacks on our virtual display.
+                if (stackInfo.displayId != displayId) {
+                    continue;
+                }
+                // Found the topmost stack on target display.
+                return stackInfo;
+            }
+            return null;
+        }
+    }
+}
diff --git a/android/app/AlarmManager.java b/android/app/AlarmManager.java
new file mode 100644
index 0000000..3a34b79
--- /dev/null
+++ b/android/app/AlarmManager.java
@@ -0,0 +1,1160 @@
+/*
+ * 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.app;
+
+import android.annotation.IntDef;
+import android.annotation.RequiresPermission;
+import android.annotation.SdkConstant;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build;
+import android.os.Handler;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.WorkSource;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.proto.ProtoOutputStream;
+
+import libcore.timezone.ZoneInfoDB;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class provides access to the system alarm services.  These allow you
+ * to schedule your application to be run at some point in the future.  When
+ * an alarm goes off, the {@link Intent} that had been registered for it
+ * is broadcast by the system, automatically starting the target application
+ * if it is not already running.  Registered alarms are retained while the
+ * device is asleep (and can optionally wake the device up if they go off
+ * during that time), but will be cleared if it is turned off and rebooted.
+ *
+ * <p>The Alarm Manager holds a CPU wake lock as long as the alarm receiver's
+ * onReceive() method is executing. This guarantees that the phone will not sleep
+ * until you have finished handling the broadcast. Once onReceive() returns, the
+ * Alarm Manager releases this wake lock. This means that the phone will in some
+ * cases sleep as soon as your onReceive() method completes.  If your alarm receiver
+ * called {@link android.content.Context#startService Context.startService()}, it
+ * is possible that the phone will sleep before the requested service is launched.
+ * To prevent this, your BroadcastReceiver and Service will need to implement a
+ * separate wake lock policy to ensure that the phone continues running until the
+ * service becomes available.
+ *
+ * <p><b>Note: The Alarm Manager is intended for cases where you want to have
+ * your application code run at a specific time, even if your application is
+ * not currently running.  For normal timing operations (ticks, timeouts,
+ * etc) it is easier and much more efficient to use
+ * {@link android.os.Handler}.</b>
+ *
+ * <p class="caution"><strong>Note:</strong> Beginning with API 19
+ * ({@link android.os.Build.VERSION_CODES#KITKAT}) alarm delivery is inexact:
+ * the OS will shift alarms in order to minimize wakeups and battery use.  There are
+ * new APIs to support applications which need strict delivery guarantees; see
+ * {@link #setWindow(int, long, long, PendingIntent)} and
+ * {@link #setExact(int, long, PendingIntent)}.  Applications whose {@code targetSdkVersion}
+ * is earlier than API 19 will continue to see the previous behavior in which all
+ * alarms are delivered exactly when requested.
+ */
+@SystemService(Context.ALARM_SERVICE)
+public class AlarmManager {
+    private static final String TAG = "AlarmManager";
+
+    /** @hide */
+    @IntDef(prefix = { "RTC", "ELAPSED" }, value = {
+            RTC_WAKEUP,
+            RTC,
+            ELAPSED_REALTIME_WAKEUP,
+            ELAPSED_REALTIME,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AlarmType {}
+
+    /**
+     * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
+     * (wall clock time in UTC), which will wake up the device when
+     * it goes off.
+     */
+    public static final int RTC_WAKEUP = 0;
+    /**
+     * Alarm time in {@link System#currentTimeMillis System.currentTimeMillis()}
+     * (wall clock time in UTC).  This alarm does not wake the
+     * device up; if it goes off while the device is asleep, it will not be
+     * delivered until the next time the device wakes up.
+     */
+    public static final int RTC = 1;
+    /**
+     * Alarm time in {@link android.os.SystemClock#elapsedRealtime
+     * SystemClock.elapsedRealtime()} (time since boot, including sleep),
+     * which will wake up the device when it goes off.
+     */
+    public static final int ELAPSED_REALTIME_WAKEUP = 2;
+    /**
+     * Alarm time in {@link android.os.SystemClock#elapsedRealtime
+     * SystemClock.elapsedRealtime()} (time since boot, including sleep).
+     * This alarm does not wake the device up; if it goes off while the device
+     * is asleep, it will not be delivered until the next time the device
+     * wakes up.
+     */
+    public static final int ELAPSED_REALTIME = 3;
+
+    /**
+     * Broadcast Action: Sent after the value returned by
+     * {@link #getNextAlarmClock()} has changed.
+     *
+     * <p class="note">This is a protected intent that can only be sent by the system.
+     * It is only sent to registered receivers.</p>
+     */
+    @SdkConstant(SdkConstant.SdkConstantType.BROADCAST_INTENT_ACTION)
+    public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED =
+            "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final long WINDOW_EXACT = 0;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final long WINDOW_HEURISTIC = -1;
+
+    /**
+     * Flag for alarms: this is to be a stand-alone alarm, that should not be batched with
+     * other alarms.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int FLAG_STANDALONE = 1<<0;
+
+    /**
+     * Flag for alarms: this alarm would like to wake the device even if it is idle.  This
+     * is, for example, an alarm for an alarm clock.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int FLAG_WAKE_FROM_IDLE = 1<<1;
+
+    /**
+     * Flag for alarms: this alarm would like to still execute even if the device is
+     * idle.  This won't bring the device out of idle, just allow this specific alarm to
+     * run.  Note that this means the actual time this alarm goes off can be inconsistent
+     * with the time of non-allow-while-idle alarms (it could go earlier than the time
+     * requested by another alarm).
+     *
+     * @hide
+     */
+    public static final int FLAG_ALLOW_WHILE_IDLE = 1<<2;
+
+    /**
+     * Flag for alarms: same as {@link #FLAG_ALLOW_WHILE_IDLE}, but doesn't have restrictions
+     * on how frequently it can be scheduled.  Only available (and automatically applied) to
+     * system alarms.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int FLAG_ALLOW_WHILE_IDLE_UNRESTRICTED = 1<<3;
+
+    /**
+     * Flag for alarms: this alarm marks the point where we would like to come out of idle
+     * mode.  It may be moved by the alarm manager to match the first wake-from-idle alarm.
+     * Scheduling an alarm with this flag puts the alarm manager in to idle mode, where it
+     * avoids scheduling any further alarms until the marker alarm is executed.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static final int FLAG_IDLE_UNTIL = 1<<4;
+
+    @UnsupportedAppUsage
+    private final IAlarmManager mService;
+    private final Context mContext;
+    private final String mPackageName;
+    private final boolean mAlwaysExact;
+    private final int mTargetSdkVersion;
+    private final Handler mMainThreadHandler;
+
+    /**
+     * Direct-notification alarms: the requester must be running continuously from the
+     * time the alarm is set to the time it is delivered, or delivery will fail.  Only
+     * one-shot alarms can be set using this mechanism, not repeating alarms.
+     */
+    public interface OnAlarmListener {
+        /**
+         * Callback method that is invoked by the system when the alarm time is reached.
+         */
+        public void onAlarm();
+    }
+
+    final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
+        final OnAlarmListener mListener;
+        Handler mHandler;
+        IAlarmCompleteListener mCompletion;
+
+        public ListenerWrapper(OnAlarmListener listener) {
+            mListener = listener;
+        }
+
+        public void setHandler(Handler h) {
+           mHandler = h;
+        }
+
+        public void cancel() {
+            try {
+                mService.remove(null, this);
+            } catch (RemoteException ex) {
+                throw ex.rethrowFromSystemServer();
+            }
+
+            synchronized (AlarmManager.class) {
+                if (sWrappers != null) {
+                    sWrappers.remove(mListener);
+                }
+            }
+        }
+
+        @Override
+        public void doAlarm(IAlarmCompleteListener alarmManager) {
+            mCompletion = alarmManager;
+
+            // Remove this listener from the wrapper cache first; the server side
+            // already considers it gone
+            synchronized (AlarmManager.class) {
+                if (sWrappers != null) {
+                    sWrappers.remove(mListener);
+                }
+            }
+
+            mHandler.post(this);
+        }
+
+        @Override
+        public void run() {
+            // Now deliver it to the app
+            try {
+                mListener.onAlarm();
+            } finally {
+                // No catch -- make sure to report completion to the system process,
+                // but continue to allow the exception to crash the app.
+
+                try {
+                    mCompletion.alarmComplete(this);
+                } catch (Exception e) {
+                    Log.e(TAG, "Unable to report completion to Alarm Manager!", e);
+                }
+            }
+        }
+    }
+
+    // Tracking of the OnAlarmListener -> wrapper mapping, for cancel() support.
+    // Access is synchronized on the AlarmManager class object.
+    private static ArrayMap<OnAlarmListener, ListenerWrapper> sWrappers;
+
+    /**
+     * package private on purpose
+     */
+    AlarmManager(IAlarmManager service, Context ctx) {
+        mService = service;
+
+        mContext = ctx;
+        mPackageName = ctx.getPackageName();
+        mTargetSdkVersion = ctx.getApplicationInfo().targetSdkVersion;
+        mAlwaysExact = (mTargetSdkVersion < Build.VERSION_CODES.KITKAT);
+        mMainThreadHandler = new Handler(ctx.getMainLooper());
+    }
+
+    private long legacyExactLength() {
+        return (mAlwaysExact ? WINDOW_EXACT : WINDOW_HEURISTIC);
+    }
+
+    /**
+     * <p>Schedule an alarm.  <b>Note: for timing operations (ticks, timeouts,
+     * etc) it is easier and much more efficient to use {@link android.os.Handler}.</b>
+     * If there is already an alarm scheduled for the same IntentSender, that previous
+     * alarm will first be canceled.
+     *
+     * <p>If the stated trigger time is in the past, the alarm will be triggered
+     * immediately.  If there is already an alarm for this Intent
+     * scheduled (with the equality of two intents being defined by
+     * {@link Intent#filterEquals}), then it will be removed and replaced by
+     * this one.
+     *
+     * <p>
+     * The alarm is an Intent broadcast that goes to a broadcast receiver that
+     * you registered with {@link android.content.Context#registerReceiver}
+     * or through the &lt;receiver&gt; tag in an AndroidManifest.xml file.
+     *
+     * <p>
+     * Alarm intents are delivered with a data extra of type int called
+     * {@link Intent#EXTRA_ALARM_COUNT Intent.EXTRA_ALARM_COUNT} that indicates
+     * how many past alarm events have been accumulated into this intent
+     * broadcast.  Recurring alarms that have gone undelivered because the
+     * phone was asleep may have a count greater than one when delivered.
+     *
+     * <div class="note">
+     * <p>
+     * <b>Note:</b> Beginning in API 19, the trigger time passed to this method
+     * is treated as inexact: the alarm will not be delivered before this time, but
+     * may be deferred and delivered some time later.  The OS will use
+     * this policy in order to "batch" alarms together across the entire system,
+     * minimizing the number of times the device needs to "wake up" and minimizing
+     * battery use.  In general, alarms scheduled in the near future will not
+     * be deferred as long as alarms scheduled far in the future.
+     *
+     * <p>
+     * With the new batching policy, delivery ordering guarantees are not as
+     * strong as they were previously.  If the application sets multiple alarms,
+     * it is possible that these alarms' <em>actual</em> delivery ordering may not match
+     * the order of their <em>requested</em> delivery times.  If your application has
+     * strong ordering requirements there are other APIs that you can use to get
+     * the necessary behavior; see {@link #setWindow(int, long, long, PendingIntent)}
+     * and {@link #setExact(int, long, PendingIntent)}.
+     *
+     * <p>
+     * Applications whose {@code targetSdkVersion} is before API 19 will
+     * continue to get the previous alarm behavior: all of their scheduled alarms
+     * will be treated as exact.
+     * </div>
+     *
+     * @param type type of alarm.
+     * @param triggerAtMillis time in milliseconds that the alarm should go
+     * off, using the appropriate clock (depending on the alarm type).
+     * @param operation Action to perform when the alarm goes off;
+     * typically comes from {@link PendingIntent#getBroadcast
+     * IntentSender.getBroadcast()}.
+     *
+     * @see android.os.Handler
+     * @see #setExact
+     * @see #setRepeating
+     * @see #setWindow
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     */
+    public void set(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
+        setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
+                null, null, null);
+    }
+
+    /**
+     * Direct callback version of {@link #set(int, long, PendingIntent)}.  Rather than
+     * supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     *
+     * @param type type of alarm.
+     * @param triggerAtMillis time in milliseconds that the alarm should go
+     *         off, using the appropriate clock (depending on the alarm type).
+     * @param tag string describing the alarm, used for logging and battery-use
+     *         attribution
+     * @param listener {@link OnAlarmListener} instance whose
+     *         {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     *         called when the alarm time is reached.  A given OnAlarmListener instance can
+     *         only be the target of a single pending alarm, just as a given PendingIntent
+     *         can only be used with one alarm at a time.
+     * @param targetHandler {@link Handler} on which to execute the listener's onAlarm()
+     *         callback, or {@code null} to run that callback on the main looper.
+     */
+    public void set(@AlarmType int type, long triggerAtMillis, String tag, OnAlarmListener listener,
+            Handler targetHandler) {
+        setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, null, listener, tag,
+                targetHandler, null, null);
+    }
+
+    /**
+     * Schedule a repeating alarm.  <b>Note: for timing operations (ticks,
+     * timeouts, etc) it is easier and much more efficient to use
+     * {@link android.os.Handler}.</b>  If there is already an alarm scheduled
+     * for the same IntentSender, it will first be canceled.
+     *
+     * <p>Like {@link #set}, except you can also supply a period at which
+     * the alarm will automatically repeat.  This alarm continues
+     * repeating until explicitly removed with {@link #cancel}.  If the stated
+     * trigger time is in the past, the alarm will be triggered immediately, with an
+     * alarm count depending on how far in the past the trigger time is relative
+     * to the repeat interval.
+     *
+     * <p>If an alarm is delayed (by system sleep, for example, for non
+     * _WAKEUP alarm types), a skipped repeat will be delivered as soon as
+     * possible.  After that, future alarms will be delivered according to the
+     * original schedule; they do not drift over time.  For example, if you have
+     * set a recurring alarm for the top of every hour but the phone was asleep
+     * from 7:45 until 8:45, an alarm will be sent as soon as the phone awakens,
+     * then the next alarm will be sent at 9:00.
+     *
+     * <p>If your application wants to allow the delivery times to drift in
+     * order to guarantee that at least a certain time interval always elapses
+     * between alarms, then the approach to take is to use one-time alarms,
+     * scheduling the next one yourself when handling each alarm delivery.
+     *
+     * <p class="note">
+     * <b>Note:</b> as of API 19, all repeating alarms are inexact.  If your
+     * application needs precise delivery times then it must use one-time
+     * exact alarms, rescheduling each time as described above. Legacy applications
+     * whose {@code targetSdkVersion} is earlier than API 19 will continue to have all
+     * of their alarms, including repeating alarms, treated as exact.
+     *
+     * @param type type of alarm.
+     * @param triggerAtMillis time in milliseconds that the alarm should first
+     * go off, using the appropriate clock (depending on the alarm type).
+     * @param intervalMillis interval in milliseconds between subsequent repeats
+     * of the alarm.
+     * @param operation Action to perform when the alarm goes off;
+     * typically comes from {@link PendingIntent#getBroadcast
+     * IntentSender.getBroadcast()}.
+     *
+     * @see android.os.Handler
+     * @see #set
+     * @see #setExact
+     * @see #setWindow
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     */
+    public void setRepeating(@AlarmType int type, long triggerAtMillis,
+            long intervalMillis, PendingIntent operation) {
+        setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
+                null, null, null, null, null);
+    }
+
+    /**
+     * Schedule an alarm to be delivered within a given window of time.  This method
+     * is similar to {@link #set(int, long, PendingIntent)}, but allows the
+     * application to precisely control the degree to which its delivery might be
+     * adjusted by the OS. This method allows an application to take advantage of the
+     * battery optimizations that arise from delivery batching even when it has
+     * modest timeliness requirements for its alarms.
+     *
+     * <p>
+     * This method can also be used to achieve strict ordering guarantees among
+     * multiple alarms by ensuring that the windows requested for each alarm do
+     * not intersect.
+     *
+     * <p>
+     * When precise delivery is not required, applications should use the standard
+     * {@link #set(int, long, PendingIntent)} method.  This will give the OS the most
+     * flexibility to minimize wakeups and battery use.  For alarms that must be delivered
+     * at precisely-specified times with no acceptable variation, applications can use
+     * {@link #setExact(int, long, PendingIntent)}.
+     *
+     * @param type type of alarm.
+     * @param windowStartMillis The earliest time, in milliseconds, that the alarm should
+     *        be delivered, expressed in the appropriate clock's units (depending on the alarm
+     *        type).
+     * @param windowLengthMillis The length of the requested delivery window,
+     *        in milliseconds.  The alarm will be delivered no later than this many
+     *        milliseconds after {@code windowStartMillis}.  Note that this parameter
+     *        is a <i>duration,</i> not the timestamp of the end of the window.
+     * @param operation Action to perform when the alarm goes off;
+     *        typically comes from {@link PendingIntent#getBroadcast
+     *        IntentSender.getBroadcast()}.
+     *
+     * @see #set
+     * @see #setExact
+     * @see #setRepeating
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     */
+    public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+            PendingIntent operation) {
+        setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation,
+                null, null, null, null, null);
+    }
+
+    /**
+     * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}.  Rather
+     * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     */
+    public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+            String tag, OnAlarmListener listener, Handler targetHandler) {
+        setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+                targetHandler, null, null);
+    }
+
+    /**
+     * Schedule an alarm to be delivered precisely at the stated time.
+     *
+     * <p>
+     * This method is like {@link #set(int, long, PendingIntent)}, but does not permit
+     * the OS to adjust the delivery time.  The alarm will be delivered as nearly as
+     * possible to the requested trigger time.
+     *
+     * <p>
+     * <b>Note:</b> only alarms for which there is a strong demand for exact-time
+     * delivery (such as an alarm clock ringing at the requested time) should be
+     * scheduled as exact.  Applications are strongly discouraged from using exact
+     * alarms unnecessarily as they reduce the OS's ability to minimize battery use.
+     *
+     * @param type type of alarm.
+     * @param triggerAtMillis time in milliseconds that the alarm should go
+     *        off, using the appropriate clock (depending on the alarm type).
+     * @param operation Action to perform when the alarm goes off;
+     *        typically comes from {@link PendingIntent#getBroadcast
+     *        IntentSender.getBroadcast()}.
+     *
+     * @see #set
+     * @see #setRepeating
+     * @see #setWindow
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     */
+    public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, null,
+                null, null);
+    }
+
+    /**
+     * Direct callback version of {@link #setExact(int, long, PendingIntent)}.  Rather
+     * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     */
+    public void setExact(@AlarmType int type, long triggerAtMillis, String tag,
+            OnAlarmListener listener, Handler targetHandler) {
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag,
+                targetHandler, null, null);
+    }
+
+    /**
+     * Schedule an idle-until alarm, which will keep the alarm manager idle until
+     * the given time.
+     * @hide
+     */
+    public void setIdleUntil(@AlarmType int type, long triggerAtMillis, String tag,
+            OnAlarmListener listener, Handler targetHandler) {
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, null,
+                listener, tag, targetHandler, null, null);
+    }
+
+    /**
+     * Schedule an alarm that represents an alarm clock, which will be used to notify the user
+     * when it goes off.  The expectation is that when this alarm triggers, the application will
+     * further wake up the device to tell the user about the alarm -- turning on the screen,
+     * playing a sound, vibrating, etc.  As such, the system will typically also use the
+     * information supplied here to tell the user about this upcoming alarm if appropriate.
+     *
+     * <p>Due to the nature of this kind of alarm, similar to {@link #setExactAndAllowWhileIdle},
+     * these alarms will be allowed to trigger even if the system is in a low-power idle
+     * (a.k.a. doze) mode.  The system may also do some prep-work when it sees that such an
+     * alarm coming up, to reduce the amount of background work that could happen if this
+     * causes the device to fully wake up -- this is to avoid situations such as a large number
+     * of devices having an alarm set at the same time in the morning, all waking up at that
+     * time and suddenly swamping the network with pending background work.  As such, these
+     * types of alarms can be extremely expensive on battery use and should only be used for
+     * their intended purpose.</p>
+     *
+     * <p>
+     * This method is like {@link #setExact(int, long, PendingIntent)}, but implies
+     * {@link #RTC_WAKEUP}.
+     *
+     * @param info
+     * @param operation Action to perform when the alarm goes off;
+     *        typically comes from {@link PendingIntent#getBroadcast
+     *        IntentSender.getBroadcast()}.
+     *
+     * @see #set
+     * @see #setRepeating
+     * @see #setWindow
+     * @see #setExact
+     * @see #cancel
+     * @see #getNextAlarmClock()
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     */
+    public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
+        setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
+                null, null, null, null, info);
+    }
+
+    /** @hide */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+    public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
+            long intervalMillis, PendingIntent operation, WorkSource workSource) {
+        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null,
+                null, workSource, null);
+    }
+
+    /**
+     * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
+     * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
+     * <p>
+     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
+            long intervalMillis, String tag, OnAlarmListener listener, Handler targetHandler,
+            WorkSource workSource) {
+        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, tag,
+                targetHandler, workSource, null);
+    }
+
+    /**
+     * Direct callback version of {@link #set(int, long, long, long, PendingIntent, WorkSource)}.
+     * Note that repeating alarms must use the PendingIntent variant, not an OnAlarmListener.
+     * <p>
+     * The OnAlarmListener's {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Handler, or on the application's main looper
+     * if {@code null} is passed as the {@code targetHandler} parameter.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+    public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
+            long intervalMillis, OnAlarmListener listener, Handler targetHandler,
+            WorkSource workSource) {
+        setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
+                targetHandler, workSource, null);
+    }
+
+    private void setImpl(@AlarmType int type, long triggerAtMillis, long windowMillis,
+            long intervalMillis, int flags, PendingIntent operation, final OnAlarmListener listener,
+            String listenerTag, Handler targetHandler, WorkSource workSource,
+            AlarmClockInfo alarmClock) {
+        if (triggerAtMillis < 0) {
+            /* NOTYET
+            if (mAlwaysExact) {
+                // Fatal error for KLP+ apps to use negative trigger times
+                throw new IllegalArgumentException("Invalid alarm trigger time "
+                        + triggerAtMillis);
+            }
+            */
+            triggerAtMillis = 0;
+        }
+
+        ListenerWrapper recipientWrapper = null;
+        if (listener != null) {
+            synchronized (AlarmManager.class) {
+                if (sWrappers == null) {
+                    sWrappers = new ArrayMap<OnAlarmListener, ListenerWrapper>();
+                }
+
+                recipientWrapper = sWrappers.get(listener);
+                // no existing wrapper => build a new one
+                if (recipientWrapper == null) {
+                    recipientWrapper = new ListenerWrapper(listener);
+                    sWrappers.put(listener, recipientWrapper);
+                }
+            }
+
+            final Handler handler = (targetHandler != null) ? targetHandler : mMainThreadHandler;
+            recipientWrapper.setHandler(handler);
+        }
+
+        try {
+            mService.set(mPackageName, type, triggerAtMillis, windowMillis, intervalMillis, flags,
+                    operation, recipientWrapper, listenerTag, workSource, alarmClock);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Available inexact recurrence interval recognized by
+     * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+     * when running on Android prior to API 19.
+     */
+    public static final long INTERVAL_FIFTEEN_MINUTES = 15 * 60 * 1000;
+
+    /**
+     * Available inexact recurrence interval recognized by
+     * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+     * when running on Android prior to API 19.
+     */
+    public static final long INTERVAL_HALF_HOUR = 2*INTERVAL_FIFTEEN_MINUTES;
+
+    /**
+     * Available inexact recurrence interval recognized by
+     * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+     * when running on Android prior to API 19.
+     */
+    public static final long INTERVAL_HOUR = 2*INTERVAL_HALF_HOUR;
+
+    /**
+     * Available inexact recurrence interval recognized by
+     * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+     * when running on Android prior to API 19.
+     */
+    public static final long INTERVAL_HALF_DAY = 12*INTERVAL_HOUR;
+
+    /**
+     * Available inexact recurrence interval recognized by
+     * {@link #setInexactRepeating(int, long, long, PendingIntent)}
+     * when running on Android prior to API 19.
+     */
+    public static final long INTERVAL_DAY = 2*INTERVAL_HALF_DAY;
+
+    /**
+     * Schedule a repeating alarm that has inexact trigger time requirements;
+     * for example, an alarm that repeats every hour, but not necessarily at
+     * the top of every hour.  These alarms are more power-efficient than
+     * the strict recurrences traditionally supplied by {@link #setRepeating}, since the
+     * system can adjust alarms' delivery times to cause them to fire simultaneously,
+     * avoiding waking the device from sleep more than necessary.
+     *
+     * <p>Your alarm's first trigger will not be before the requested time,
+     * but it might not occur for almost a full interval after that time.  In
+     * addition, while the overall period of the repeating alarm will be as
+     * requested, the time between any two successive firings of the alarm
+     * may vary.  If your application demands very low jitter, use
+     * one-shot alarms with an appropriate window instead; see {@link
+     * #setWindow(int, long, long, PendingIntent)} and
+     * {@link #setExact(int, long, PendingIntent)}.
+     *
+     * <p class="note">
+     * As of API 19, all repeating alarms are inexact.  Because this method has
+     * been available since API 3, your application can safely call it and be
+     * assured that it will get similar behavior on both current and older versions
+     * of Android.
+     *
+     * @param type type of alarm.
+     * @param triggerAtMillis time in milliseconds that the alarm should first
+     * go off, using the appropriate clock (depending on the alarm type).  This
+     * is inexact: the alarm will not fire before this time, but there may be a
+     * delay of almost an entire alarm interval before the first invocation of
+     * the alarm.
+     * @param intervalMillis interval in milliseconds between subsequent repeats
+     * of the alarm.  Prior to API 19, if this is one of INTERVAL_FIFTEEN_MINUTES,
+     * INTERVAL_HALF_HOUR, INTERVAL_HOUR, INTERVAL_HALF_DAY, or INTERVAL_DAY
+     * then the alarm will be phase-aligned with other alarms to reduce the
+     * number of wakeups.  Otherwise, the alarm will be set as though the
+     * application had called {@link #setRepeating}.  As of API 19, all repeating
+     * alarms will be inexact and subject to batching with other alarms regardless
+     * of their stated repeat interval.
+     * @param operation Action to perform when the alarm goes off;
+     * typically comes from {@link PendingIntent#getBroadcast
+     * IntentSender.getBroadcast()}.
+     *
+     * @see android.os.Handler
+     * @see #set
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     * @see #INTERVAL_FIFTEEN_MINUTES
+     * @see #INTERVAL_HALF_HOUR
+     * @see #INTERVAL_HOUR
+     * @see #INTERVAL_HALF_DAY
+     * @see #INTERVAL_DAY
+     */
+    public void setInexactRepeating(@AlarmType int type, long triggerAtMillis,
+            long intervalMillis, PendingIntent operation) {
+        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null,
+                null, null, null, null);
+    }
+
+    /**
+     * Like {@link #set(int, long, PendingIntent)}, but this alarm will be allowed to execute
+     * even when the system is in low-power idle (a.k.a. doze) modes.  This type of alarm must
+     * <b>only</b> be used for situations where it is actually required that the alarm go off while
+     * in idle -- a reasonable example would be for a calendar notification that should make a
+     * sound so the user is aware of it.  When the alarm is dispatched, the app will also be
+     * added to the system's temporary whitelist for approximately 10 seconds to allow that
+     * application to acquire further wake locks in which to complete its work.</p>
+     *
+     * <p>These alarms can significantly impact the power use
+     * of the device when idle (and thus cause significant battery blame to the app scheduling
+     * them), so they should be used with care.  To reduce abuse, there are restrictions on how
+     * frequently these alarms will go off for a particular application.
+     * Under normal system operation, it will not dispatch these
+     * alarms more than about every minute (at which point every such pending alarm is
+     * dispatched); when in low-power idle modes this duration may be significantly longer,
+     * such as 15 minutes.</p>
+     *
+     * <p>Unlike other alarms, the system is free to reschedule this type of alarm to happen
+     * out of order with any other alarms, even those from the same app.  This will clearly happen
+     * when the device is idle (since this alarm can go off while idle, when any other alarms
+     * from the app will be held until later), but may also happen even when not idle.</p>
+     *
+     * <p>Regardless of the app's target SDK version, this call always allows batching of the
+     * alarm.</p>
+     *
+     * @param type type of alarm.
+     * @param triggerAtMillis time in milliseconds that the alarm should go
+     * off, using the appropriate clock (depending on the alarm type).
+     * @param operation Action to perform when the alarm goes off;
+     * typically comes from {@link PendingIntent#getBroadcast
+     * IntentSender.getBroadcast()}.
+     *
+     * @see #set(int, long, PendingIntent)
+     * @see #setExactAndAllowWhileIdle
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     */
+    public void setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
+            PendingIntent operation) {
+        setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
+                operation, null, null, null, null, null);
+    }
+
+    /**
+     * Like {@link #setExact(int, long, PendingIntent)}, but this alarm will be allowed to execute
+     * even when the system is in low-power idle modes.  If you don't need exact scheduling of
+     * the alarm but still need to execute while idle, consider using
+     * {@link #setAndAllowWhileIdle}.  This type of alarm must <b>only</b>
+     * be used for situations where it is actually required that the alarm go off while in
+     * idle -- a reasonable example would be for a calendar notification that should make a
+     * sound so the user is aware of it.  When the alarm is dispatched, the app will also be
+     * added to the system's temporary whitelist for approximately 10 seconds to allow that
+     * application to acquire further wake locks in which to complete its work.</p>
+     *
+     * <p>These alarms can significantly impact the power use
+     * of the device when idle (and thus cause significant battery blame to the app scheduling
+     * them), so they should be used with care.  To reduce abuse, there are restrictions on how
+     * frequently these alarms will go off for a particular application.
+     * Under normal system operation, it will not dispatch these
+     * alarms more than about every minute (at which point every such pending alarm is
+     * dispatched); when in low-power idle modes this duration may be significantly longer,
+     * such as 15 minutes.</p>
+     *
+     * <p>Unlike other alarms, the system is free to reschedule this type of alarm to happen
+     * out of order with any other alarms, even those from the same app.  This will clearly happen
+     * when the device is idle (since this alarm can go off while idle, when any other alarms
+     * from the app will be held until later), but may also happen even when not idle.
+     * Note that the OS will allow itself more flexibility for scheduling these alarms than
+     * regular exact alarms, since the application has opted into this behavior.  When the
+     * device is idle it may take even more liberties with scheduling in order to optimize
+     * for battery life.</p>
+     *
+     * @param type type of alarm.
+     * @param triggerAtMillis time in milliseconds that the alarm should go
+     *        off, using the appropriate clock (depending on the alarm type).
+     * @param operation Action to perform when the alarm goes off;
+     *        typically comes from {@link PendingIntent#getBroadcast
+     *        IntentSender.getBroadcast()}.
+     *
+     * @see #set
+     * @see #setRepeating
+     * @see #setWindow
+     * @see #cancel
+     * @see android.content.Context#sendBroadcast
+     * @see android.content.Context#registerReceiver
+     * @see android.content.Intent#filterEquals
+     * @see #ELAPSED_REALTIME
+     * @see #ELAPSED_REALTIME_WAKEUP
+     * @see #RTC
+     * @see #RTC_WAKEUP
+     */
+    public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
+            PendingIntent operation) {
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
+                null, null, null, null, null);
+    }
+
+    /**
+     * Remove any alarms with a matching {@link Intent}.
+     * Any alarm, of any type, whose Intent matches this one (as defined by
+     * {@link Intent#filterEquals}), will be canceled.
+     *
+     * @param operation IntentSender which matches a previously added
+     * IntentSender. This parameter must not be {@code null}.
+     *
+     * @see #set
+     */
+    public void cancel(PendingIntent operation) {
+        if (operation == null) {
+            final String msg = "cancel() called with a null PendingIntent";
+            if (mTargetSdkVersion >= Build.VERSION_CODES.N) {
+                throw new NullPointerException(msg);
+            } else {
+                Log.e(TAG, msg);
+                return;
+            }
+        }
+
+        try {
+            mService.remove(operation, null);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Remove any alarm scheduled to be delivered to the given {@link OnAlarmListener}.
+     *
+     * @param listener OnAlarmListener instance that is the target of a currently-set alarm.
+     */
+    public void cancel(OnAlarmListener listener) {
+        if (listener == null) {
+            throw new NullPointerException("cancel() called with a null OnAlarmListener");
+        }
+
+        ListenerWrapper wrapper = null;
+        synchronized (AlarmManager.class) {
+            if (sWrappers != null) {
+                wrapper = sWrappers.get(listener);
+            }
+        }
+
+        if (wrapper == null) {
+            Log.w(TAG, "Unrecognized alarm listener " + listener);
+            return;
+        }
+
+        wrapper.cancel();
+    }
+
+    /**
+     * Set the system wall clock time.
+     * Requires the permission android.permission.SET_TIME.
+     *
+     * @param millis time in milliseconds since the Epoch
+     */
+    @RequiresPermission(android.Manifest.permission.SET_TIME)
+    public void setTime(long millis) {
+        try {
+            mService.setTime(millis);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets the system's persistent default time zone. This is the time zone for all apps, even
+     * after a reboot. Use {@link java.util.TimeZone#setDefault} if you just want to change the
+     * time zone within your app, and even then prefer to pass an explicit
+     * {@link java.util.TimeZone} to APIs that require it rather than changing the time zone for
+     * all threads.
+     *
+     * <p> On android M and above, it is an error to pass in a non-Olson timezone to this
+     * function. Note that this is a bad idea on all Android releases because POSIX and
+     * the {@code TimeZone} class have opposite interpretations of {@code '+'} and {@code '-'}
+     * in the same non-Olson ID.
+     *
+     * @param timeZone one of the Olson ids from the list returned by
+     *     {@link java.util.TimeZone#getAvailableIDs}
+     */
+    @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE)
+    public void setTimeZone(String timeZone) {
+        if (TextUtils.isEmpty(timeZone)) {
+            return;
+        }
+
+        // Reject this timezone if it isn't an Olson zone we recognize.
+        if (mTargetSdkVersion >= Build.VERSION_CODES.M) {
+            boolean hasTimeZone = false;
+            try {
+                hasTimeZone = ZoneInfoDB.getInstance().hasTimeZone(timeZone);
+            } catch (IOException ignored) {
+            }
+
+            if (!hasTimeZone) {
+                throw new IllegalArgumentException("Timezone: " + timeZone + " is not an Olson ID");
+            }
+        }
+
+        try {
+            mService.setTimeZone(timeZone);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public long getNextWakeFromIdleTime() {
+        try {
+            return mService.getNextWakeFromIdleTime();
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets information about the next alarm clock currently scheduled.
+     *
+     * The alarm clocks considered are those scheduled by any application
+     * using the {@link #setAlarmClock} method.
+     *
+     * @return An {@link AlarmClockInfo} object describing the next upcoming alarm
+     *   clock event that will occur.  If there are no alarm clock events currently
+     *   scheduled, this method will return {@code null}.
+     *
+     * @see #setAlarmClock
+     * @see AlarmClockInfo
+     * @see #ACTION_NEXT_ALARM_CLOCK_CHANGED
+     */
+    public AlarmClockInfo getNextAlarmClock() {
+        return getNextAlarmClock(mContext.getUserId());
+    }
+
+    /**
+     * Gets information about the next alarm clock currently scheduled.
+     *
+     * The alarm clocks considered are those scheduled by any application
+     * using the {@link #setAlarmClock} method within the given user.
+     *
+     * @return An {@link AlarmClockInfo} object describing the next upcoming alarm
+     *   clock event that will occur within the given user.  If there are no alarm clock
+     *   events currently scheduled in that user, this method will return {@code null}.
+     *
+     * @see #setAlarmClock
+     * @see AlarmClockInfo
+     * @see #ACTION_NEXT_ALARM_CLOCK_CHANGED
+     *
+     * @hide
+     */
+    public AlarmClockInfo getNextAlarmClock(int userId) {
+        try {
+            return mService.getNextAlarmClock(userId);
+        } catch (RemoteException ex) {
+            throw ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * An immutable description of a scheduled "alarm clock" event.
+     *
+     * @see AlarmManager#setAlarmClock
+     * @see AlarmManager#getNextAlarmClock
+     */
+    public static final class AlarmClockInfo implements Parcelable {
+
+        private final long mTriggerTime;
+        private final PendingIntent mShowIntent;
+
+        /**
+         * Creates a new alarm clock description.
+         *
+         * @param triggerTime time at which the underlying alarm is triggered in wall time
+         *                    milliseconds since the epoch
+         * @param showIntent an intent that can be used to show or edit details of
+         *                        the alarm clock.
+         */
+        public AlarmClockInfo(long triggerTime, PendingIntent showIntent) {
+            mTriggerTime = triggerTime;
+            mShowIntent = showIntent;
+        }
+
+        /**
+         * Use the {@link #CREATOR}
+         * @hide
+         */
+        AlarmClockInfo(Parcel in) {
+            mTriggerTime = in.readLong();
+            mShowIntent = in.readParcelable(PendingIntent.class.getClassLoader());
+        }
+
+        /**
+         * Returns the time at which the alarm is going to trigger.
+         *
+         * This value is UTC wall clock time in milliseconds, as returned by
+         * {@link System#currentTimeMillis()} for example.
+         */
+        public long getTriggerTime() {
+            return mTriggerTime;
+        }
+
+        /**
+         * Returns an intent that can be used to show or edit details of the alarm clock in
+         * the application that scheduled it.
+         *
+         * <p class="note">Beware that any application can retrieve and send this intent,
+         * potentially with additional fields filled in. See
+         * {@link PendingIntent#send(android.content.Context, int, android.content.Intent)
+         * PendingIntent.send()} and {@link android.content.Intent#fillIn Intent.fillIn()}
+         * for details.
+         */
+        public PendingIntent getShowIntent() {
+            return mShowIntent;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(mTriggerTime);
+            dest.writeParcelable(mShowIntent, flags);
+        }
+
+        public static final @android.annotation.NonNull Creator<AlarmClockInfo> CREATOR = new Creator<AlarmClockInfo>() {
+            @Override
+            public AlarmClockInfo createFromParcel(Parcel in) {
+                return new AlarmClockInfo(in);
+            }
+
+            @Override
+            public AlarmClockInfo[] newArray(int size) {
+                return new AlarmClockInfo[size];
+            }
+        };
+
+        /** @hide */
+        public void writeToProto(ProtoOutputStream proto, long fieldId) {
+            final long token = proto.start(fieldId);
+            proto.write(AlarmClockInfoProto.TRIGGER_TIME_MS, mTriggerTime);
+            if (mShowIntent != null) {
+                mShowIntent.writeToProto(proto, AlarmClockInfoProto.SHOW_INTENT);
+            }
+            proto.end(token);
+        }
+    }
+}
diff --git a/android/app/AlertDialog.java b/android/app/AlertDialog.java
new file mode 100644
index 0000000..bfc216a
--- /dev/null
+++ b/android/app/AlertDialog.java
@@ -0,0 +1,1136 @@
+/*
+ * 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.app;
+
+import android.annotation.ArrayRes;
+import android.annotation.AttrRes;
+import android.annotation.DrawableRes;
+import android.annotation.StringRes;
+import android.annotation.StyleRes;
+import android.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.ResourceId;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.os.Message;
+import android.text.Layout;
+import android.text.method.MovementMethod;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.Button;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+
+import com.android.internal.R;
+import com.android.internal.app.AlertController;
+
+/**
+ * A subclass of Dialog that can display one, two or three buttons. If you only want to
+ * display a String in this dialog box, use the setMessage() method.  If you
+ * want to display a more complex view, look up the FrameLayout called "custom"
+ * and add your view to it:
+ *
+ * <pre>
+ * FrameLayout fl = findViewById(android.R.id.custom);
+ * fl.addView(myView, new LayoutParams(MATCH_PARENT, WRAP_CONTENT));
+ * </pre>
+ *
+ * <p>The AlertDialog class takes care of automatically setting
+ * {@link android.view.WindowManager.LayoutParams#FLAG_ALT_FOCUSABLE_IM
+ * WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM} for you based on whether
+ * any views in the dialog return true from {@link View#onCheckIsTextEditor()
+ * View.onCheckIsTextEditor()}.  Generally you want this set for a Dialog
+ * without text editors, so that it will be placed on top of the current
+ * input method UI.  You can modify this behavior by forcing the flag to your
+ * desired mode after calling {@link #onCreate}.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating dialogs, read the
+ * <a href="{@docRoot}guide/topics/ui/dialogs.html">Dialogs</a> developer guide.</p>
+ * </div>
+ */
+public class AlertDialog extends Dialog implements DialogInterface {
+    @UnsupportedAppUsage
+    private AlertController mAlert;
+
+    /**
+     * Special theme constant for {@link #AlertDialog(Context, int)}: use
+     * the traditional (pre-Holo) alert dialog theme.
+     *
+     * @deprecated Use {@link android.R.style#Theme_Material_Dialog_Alert}.
+     */
+    @Deprecated
+    public static final int THEME_TRADITIONAL = 1;
+
+    /**
+     * Special theme constant for {@link #AlertDialog(Context, int)}: use
+     * the holographic alert theme with a dark background.
+     *
+     * @deprecated Use {@link android.R.style#Theme_Material_Dialog_Alert}.
+     */
+    @Deprecated
+    public static final int THEME_HOLO_DARK = 2;
+
+    /**
+     * Special theme constant for {@link #AlertDialog(Context, int)}: use
+     * the holographic alert theme with a light background.
+     *
+     * @deprecated Use {@link android.R.style#Theme_Material_Light_Dialog_Alert}.
+     */
+    @Deprecated
+    public static final int THEME_HOLO_LIGHT = 3;
+
+    /**
+     * Special theme constant for {@link #AlertDialog(Context, int)}: use
+     * the device's default alert theme with a dark background.
+     *
+     * @deprecated Use {@link android.R.style#Theme_DeviceDefault_Dialog_Alert}.
+     */
+    @Deprecated
+    public static final int THEME_DEVICE_DEFAULT_DARK = 4;
+
+    /**
+     * Special theme constant for {@link #AlertDialog(Context, int)}: use
+     * the device's default alert theme with a light background.
+     *
+     * @deprecated Use {@link android.R.style#Theme_DeviceDefault_Light_Dialog_Alert}.
+     */
+    @Deprecated
+    public static final int THEME_DEVICE_DEFAULT_LIGHT = 5;
+
+    /**
+     * No layout hint.
+     * @hide
+     */
+    public static final int LAYOUT_HINT_NONE = 0;
+
+    /**
+     * Hint layout to the side.
+     * @hide
+     */
+    public static final int LAYOUT_HINT_SIDE = 1;
+
+    /**
+     * Creates an alert dialog that uses the default alert dialog theme.
+     * <p>
+     * The default alert dialog theme is defined by
+     * {@link android.R.attr#alertDialogTheme} within the parent
+     * {@code context}'s theme.
+     *
+     * @param context the parent context
+     * @see android.R.styleable#Theme_alertDialogTheme
+     */
+    protected AlertDialog(Context context) {
+        this(context, 0);
+    }
+
+    /**
+     * Creates an alert dialog that uses the default alert dialog theme and a
+     * custom cancel listener.
+     * <p>
+     * This is functionally identical to:
+     * <pre>
+     *     AlertDialog dialog = new AlertDialog(context);
+     *     alertDialog.setCancelable(cancelable);
+     *     alertDialog.setOnCancelListener(cancelListener);
+     * </pre>
+     * <p>
+     * The default alert dialog theme is defined by
+     * {@link android.R.attr#alertDialogTheme} within the parent
+     * {@code context}'s theme.
+     *
+     * @param context the parent context
+     * @see android.R.styleable#Theme_alertDialogTheme
+     */
+    protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
+        this(context, 0);
+
+        setCancelable(cancelable);
+        setOnCancelListener(cancelListener);
+    }
+
+    /**
+     * Creates an alert dialog that uses an explicit theme resource.
+     * <p>
+     * The specified theme resource ({@code themeResId}) is applied on top of
+     * the parent {@code context}'s theme. It may be specified as a style
+     * resource containing a fully-populated theme, such as
+     * {@link android.R.style#Theme_Material_Dialog}, to replace all attributes
+     * in the parent {@code context}'s theme including primary and accent
+     * colors.
+     * <p>
+     * To preserve attributes such as primary and accent colors, the
+     * {@code themeResId} may instead be specified as an overlay theme such as
+     * {@link android.R.style#ThemeOverlay_Material_Dialog}. This will override
+     * only the window attributes necessary to style the alert window as a
+     * dialog.
+     * <p>
+     * Alternatively, the {@code themeResId} may be specified as {@code 0} to
+     * use the parent {@code context}'s resolved value for
+     * {@link android.R.attr#alertDialogTheme}.
+     *
+     * @param context the parent context
+     * @param themeResId the resource ID of the theme against which to inflate
+     *                   this dialog, or {@code 0} to use the parent
+     *                   {@code context}'s default alert dialog theme
+     * @see android.R.styleable#Theme_alertDialogTheme
+     */
+    protected AlertDialog(Context context, @StyleRes int themeResId) {
+        this(context, themeResId, true);
+    }
+
+    AlertDialog(Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {
+        super(context, createContextThemeWrapper ? resolveDialogTheme(context, themeResId) : 0,
+                createContextThemeWrapper);
+
+        mWindow.alwaysReadCloseOnTouchAttr();
+        mAlert = AlertController.create(getContext(), this, getWindow());
+    }
+
+    static @StyleRes int resolveDialogTheme(Context context, @StyleRes int themeResId) {
+        if (themeResId == THEME_TRADITIONAL) {
+            return R.style.Theme_Dialog_Alert;
+        } else if (themeResId == THEME_HOLO_DARK) {
+            return R.style.Theme_Holo_Dialog_Alert;
+        } else if (themeResId == THEME_HOLO_LIGHT) {
+            return R.style.Theme_Holo_Light_Dialog_Alert;
+        } else if (themeResId == THEME_DEVICE_DEFAULT_DARK) {
+            return R.style.Theme_DeviceDefault_Dialog_Alert;
+        } else if (themeResId == THEME_DEVICE_DEFAULT_LIGHT) {
+            return R.style.Theme_DeviceDefault_Light_Dialog_Alert;
+        } else if (ResourceId.isValid(themeResId)) {
+            // start of real resource IDs.
+            return themeResId;
+        } else {
+            final TypedValue outValue = new TypedValue();
+            context.getTheme().resolveAttribute(R.attr.alertDialogTheme, outValue, true);
+            return outValue.resourceId;
+        }
+    }
+
+    /**
+     * Gets one of the buttons used in the dialog. Returns null if the specified
+     * button does not exist or the dialog has not yet been fully created (for
+     * example, via {@link #show()} or {@link #create()}).
+     *
+     * @param whichButton The identifier of the button that should be returned.
+     *            For example, this can be
+     *            {@link DialogInterface#BUTTON_POSITIVE}.
+     * @return The button from the dialog, or null if a button does not exist.
+     */
+    public Button getButton(int whichButton) {
+        return mAlert.getButton(whichButton);
+    }
+
+    /**
+     * Gets the list view used in the dialog.
+     *
+     * @return The {@link ListView} from the dialog.
+     */
+    public ListView getListView() {
+        return mAlert.getListView();
+    }
+
+    @Override
+    public void setTitle(CharSequence title) {
+        super.setTitle(title);
+        mAlert.setTitle(title);
+    }
+
+    /**
+     * @see Builder#setCustomTitle(View)
+     */
+    public void setCustomTitle(View customTitleView) {
+        mAlert.setCustomTitle(customTitleView);
+    }
+
+    public void setMessage(CharSequence message) {
+        mAlert.setMessage(message);
+    }
+
+    /** @hide */
+    public void setMessageMovementMethod(MovementMethod movementMethod) {
+        mAlert.setMessageMovementMethod(movementMethod);
+    }
+
+    /** @hide */
+    public void setMessageHyphenationFrequency(
+            @Layout.HyphenationFrequency int hyphenationFrequency) {
+        mAlert.setMessageHyphenationFrequency(hyphenationFrequency);
+    }
+
+    /**
+     * Set the view to display in that dialog.
+     */
+    public void setView(View view) {
+        mAlert.setView(view);
+    }
+
+    /**
+     * Set the view to display in that dialog, specifying the spacing to appear around that
+     * view.
+     *
+     * @param view The view to show in the content area of the dialog
+     * @param viewSpacingLeft Extra space to appear to the left of {@code view}
+     * @param viewSpacingTop Extra space to appear above {@code view}
+     * @param viewSpacingRight Extra space to appear to the right of {@code view}
+     * @param viewSpacingBottom Extra space to appear below {@code view}
+     */
+    public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight,
+            int viewSpacingBottom) {
+        mAlert.setView(view, viewSpacingLeft, viewSpacingTop, viewSpacingRight, viewSpacingBottom);
+    }
+
+    /**
+     * Internal api to allow hinting for the best button panel layout.
+     * @hide
+     */
+    void setButtonPanelLayoutHint(int layoutHint) {
+        mAlert.setButtonPanelLayoutHint(layoutHint);
+    }
+
+    /**
+     * Set a message to be sent when a button is pressed.
+     *
+     * @param whichButton Which button to set the message for, can be one of
+     *            {@link DialogInterface#BUTTON_POSITIVE},
+     *            {@link DialogInterface#BUTTON_NEGATIVE}, or
+     *            {@link DialogInterface#BUTTON_NEUTRAL}
+     * @param text The text to display in positive button.
+     * @param msg The {@link Message} to be sent when clicked.
+     */
+    public void setButton(int whichButton, CharSequence text, Message msg) {
+        mAlert.setButton(whichButton, text, null, msg);
+    }
+
+    /**
+     * Set a listener to be invoked when the positive button of the dialog is pressed.
+     *
+     * @param whichButton Which button to set the listener on, can be one of
+     *            {@link DialogInterface#BUTTON_POSITIVE},
+     *            {@link DialogInterface#BUTTON_NEGATIVE}, or
+     *            {@link DialogInterface#BUTTON_NEUTRAL}
+     * @param text The text to display in positive button.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     */
+    public void setButton(int whichButton, CharSequence text, OnClickListener listener) {
+        mAlert.setButton(whichButton, text, listener, null);
+    }
+
+    /**
+     * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+     *             {@link DialogInterface#BUTTON_POSITIVE}.
+     */
+    @Deprecated
+    public void setButton(CharSequence text, Message msg) {
+        setButton(BUTTON_POSITIVE, text, msg);
+    }
+
+    /**
+     * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+     *             {@link DialogInterface#BUTTON_NEGATIVE}.
+     */
+    @Deprecated
+    public void setButton2(CharSequence text, Message msg) {
+        setButton(BUTTON_NEGATIVE, text, msg);
+    }
+
+    /**
+     * @deprecated Use {@link #setButton(int, CharSequence, Message)} with
+     *             {@link DialogInterface#BUTTON_NEUTRAL}.
+     */
+    @Deprecated
+    public void setButton3(CharSequence text, Message msg) {
+        setButton(BUTTON_NEUTRAL, text, msg);
+    }
+
+    /**
+     * Set a listener to be invoked when button 1 of the dialog is pressed.
+     *
+     * @param text The text to display in button 1.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     * @deprecated Use
+     *             {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+     *             with {@link DialogInterface#BUTTON_POSITIVE}
+     */
+    @Deprecated
+    public void setButton(CharSequence text, final OnClickListener listener) {
+        setButton(BUTTON_POSITIVE, text, listener);
+    }
+
+    /**
+     * Set a listener to be invoked when button 2 of the dialog is pressed.
+     * @param text The text to display in button 2.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     * @deprecated Use
+     *             {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+     *             with {@link DialogInterface#BUTTON_NEGATIVE}
+     */
+    @Deprecated
+    public void setButton2(CharSequence text, final OnClickListener listener) {
+        setButton(BUTTON_NEGATIVE, text, listener);
+    }
+
+    /**
+     * Set a listener to be invoked when button 3 of the dialog is pressed.
+     * @param text The text to display in button 3.
+     * @param listener The {@link DialogInterface.OnClickListener} to use.
+     * @deprecated Use
+     *             {@link #setButton(int, CharSequence, android.content.DialogInterface.OnClickListener)}
+     *             with {@link DialogInterface#BUTTON_NEUTRAL}
+     */
+    @Deprecated
+    public void setButton3(CharSequence text, final OnClickListener listener) {
+        setButton(BUTTON_NEUTRAL, text, listener);
+    }
+
+    /**
+     * Set resId to 0 if you don't want an icon.
+     * @param resId the resourceId of the drawable to use as the icon or 0
+     * if you don't want an icon.
+     */
+    public void setIcon(@DrawableRes int resId) {
+        mAlert.setIcon(resId);
+    }
+
+    public void setIcon(Drawable icon) {
+        mAlert.setIcon(icon);
+    }
+
+    /**
+     * Set an icon as supplied by a theme attribute. e.g. android.R.attr.alertDialogIcon
+     *
+     * @param attrId ID of a theme attribute that points to a drawable resource.
+     */
+    public void setIconAttribute(@AttrRes int attrId) {
+        TypedValue out = new TypedValue();
+        mContext.getTheme().resolveAttribute(attrId, out, true);
+        mAlert.setIcon(out.resourceId);
+    }
+
+    public void setInverseBackgroundForced(boolean forceInverseBackground) {
+        mAlert.setInverseBackgroundForced(forceInverseBackground);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        mAlert.installContent();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (mAlert.onKeyDown(keyCode, event)) return true;
+        return super.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        if (mAlert.onKeyUp(keyCode, event)) return true;
+        return super.onKeyUp(keyCode, event);
+    }
+
+    public static class Builder {
+        @UnsupportedAppUsage
+        private final AlertController.AlertParams P;
+
+        /**
+         * Creates a builder for an alert dialog that uses the default alert
+         * dialog theme.
+         * <p>
+         * The default alert dialog theme is defined by
+         * {@link android.R.attr#alertDialogTheme} within the parent
+         * {@code context}'s theme.
+         *
+         * @param context the parent context
+         */
+        public Builder(Context context) {
+            this(context, resolveDialogTheme(context, Resources.ID_NULL));
+        }
+
+        /**
+         * Creates a builder for an alert dialog that uses an explicit theme
+         * resource.
+         * <p>
+         * The specified theme resource ({@code themeResId}) is applied on top
+         * of the parent {@code context}'s theme. It may be specified as a
+         * style resource containing a fully-populated theme, such as
+         * {@link android.R.style#Theme_Material_Dialog}, to replace all
+         * attributes in the parent {@code context}'s theme including primary
+         * and accent colors.
+         * <p>
+         * To preserve attributes such as primary and accent colors, the
+         * {@code themeResId} may instead be specified as an overlay theme such
+         * as {@link android.R.style#ThemeOverlay_Material_Dialog}. This will
+         * override only the window attributes necessary to style the alert
+         * window as a dialog.
+         * <p>
+         * Alternatively, the {@code themeResId} may be specified as {@code 0}
+         * to use the parent {@code context}'s resolved value for
+         * {@link android.R.attr#alertDialogTheme}.
+         *
+         * @param context the parent context
+         * @param themeResId the resource ID of the theme against which to inflate
+         *                   this dialog, or {@code 0} to use the parent
+         *                   {@code context}'s default alert dialog theme
+         */
+        public Builder(Context context, int themeResId) {
+            P = new AlertController.AlertParams(new ContextThemeWrapper(
+                    context, resolveDialogTheme(context, themeResId)));
+        }
+
+        /**
+         * Returns a {@link Context} with the appropriate theme for dialogs created by this Builder.
+         * Applications should use this Context for obtaining LayoutInflaters for inflating views
+         * that will be used in the resulting dialogs, as it will cause views to be inflated with
+         * the correct theme.
+         *
+         * @return A Context for built Dialogs.
+         */
+        public Context getContext() {
+            return P.mContext;
+        }
+
+        /**
+         * Set the title using the given resource id.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setTitle(@StringRes int titleId) {
+            P.mTitle = P.mContext.getText(titleId);
+            return this;
+        }
+
+        /**
+         * Set the title displayed in the {@link Dialog}.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setTitle(CharSequence title) {
+            P.mTitle = title;
+            return this;
+        }
+
+        /**
+         * Set the title using the custom view {@code customTitleView}.
+         * <p>
+         * The methods {@link #setTitle(int)} and {@link #setIcon(int)} should
+         * be sufficient for most titles, but this is provided if the title
+         * needs more customization. Using this will replace the title and icon
+         * set via the other methods.
+         * <p>
+         * <strong>Note:</strong> To ensure consistent styling, the custom view
+         * should be inflated or constructed using the alert dialog's themed
+         * context obtained via {@link #getContext()}.
+         *
+         * @param customTitleView the custom view to use as the title
+         * @return this Builder object to allow for chaining of calls to set
+         *         methods
+         */
+        public Builder setCustomTitle(View customTitleView) {
+            P.mCustomTitleView = customTitleView;
+            return this;
+        }
+
+        /**
+         * Set the message to display using the given resource id.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setMessage(@StringRes int messageId) {
+            P.mMessage = P.mContext.getText(messageId);
+            return this;
+        }
+
+        /**
+         * Set the message to display.
+          *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setMessage(CharSequence message) {
+            P.mMessage = message;
+            return this;
+        }
+
+        /**
+         * Set the resource id of the {@link Drawable} to be used in the title.
+         * <p>
+         * Takes precedence over values set using {@link #setIcon(Drawable)}.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setIcon(@DrawableRes int iconId) {
+            P.mIconId = iconId;
+            return this;
+        }
+
+        /**
+         * Set the {@link Drawable} to be used in the title.
+         * <p>
+         * <strong>Note:</strong> To ensure consistent styling, the drawable
+         * should be inflated or constructed using the alert dialog's themed
+         * context obtained via {@link #getContext()}.
+         *
+         * @return this Builder object to allow for chaining of calls to set
+         *         methods
+         */
+        public Builder setIcon(Drawable icon) {
+            P.mIcon = icon;
+            return this;
+        }
+
+        /**
+         * Set an icon as supplied by a theme attribute. e.g.
+         * {@link android.R.attr#alertDialogIcon}.
+         * <p>
+         * Takes precedence over values set using {@link #setIcon(int)} or
+         * {@link #setIcon(Drawable)}.
+         *
+         * @param attrId ID of a theme attribute that points to a drawable resource.
+         */
+        public Builder setIconAttribute(@AttrRes int attrId) {
+            TypedValue out = new TypedValue();
+            P.mContext.getTheme().resolveAttribute(attrId, out, true);
+            P.mIconId = out.resourceId;
+            return this;
+        }
+
+        /**
+         * Set a listener to be invoked when the positive button of the dialog is pressed.
+         * @param textId The resource id of the text to display in the positive button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
+            P.mPositiveButtonText = P.mContext.getText(textId);
+            P.mPositiveButtonListener = listener;
+            return this;
+        }
+
+        /**
+         * Set a listener to be invoked when the positive button of the dialog is pressed.
+         * @param text The text to display in the positive button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
+            P.mPositiveButtonText = text;
+            P.mPositiveButtonListener = listener;
+            return this;
+        }
+
+        /**
+         * Set a listener to be invoked when the negative button of the dialog is pressed.
+         * @param textId The resource id of the text to display in the negative button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setNegativeButton(@StringRes int textId, final OnClickListener listener) {
+            P.mNegativeButtonText = P.mContext.getText(textId);
+            P.mNegativeButtonListener = listener;
+            return this;
+        }
+
+        /**
+         * Set a listener to be invoked when the negative button of the dialog is pressed.
+         * @param text The text to display in the negative button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setNegativeButton(CharSequence text, final OnClickListener listener) {
+            P.mNegativeButtonText = text;
+            P.mNegativeButtonListener = listener;
+            return this;
+        }
+
+        /**
+         * Set a listener to be invoked when the neutral button of the dialog is pressed.
+         * @param textId The resource id of the text to display in the neutral button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setNeutralButton(@StringRes int textId, final OnClickListener listener) {
+            P.mNeutralButtonText = P.mContext.getText(textId);
+            P.mNeutralButtonListener = listener;
+            return this;
+        }
+
+        /**
+         * Set a listener to be invoked when the neutral button of the dialog is pressed.
+         * @param text The text to display in the neutral button
+         * @param listener The {@link DialogInterface.OnClickListener} to use.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setNeutralButton(CharSequence text, final OnClickListener listener) {
+            P.mNeutralButtonText = text;
+            P.mNeutralButtonListener = listener;
+            return this;
+        }
+
+        /**
+         * Sets whether the dialog is cancelable or not.  Default is true.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setCancelable(boolean cancelable) {
+            P.mCancelable = cancelable;
+            return this;
+        }
+
+        /**
+         * Sets the callback that will be called if the dialog is canceled.
+         *
+         * <p>Even in a cancelable dialog, the dialog may be dismissed for reasons other than
+         * being canceled or one of the supplied choices being selected.
+         * If you are interested in listening for all cases where the dialog is dismissed
+         * and not just when it is canceled, see
+         * {@link #setOnDismissListener(android.content.DialogInterface.OnDismissListener) setOnDismissListener}.</p>
+         * @see #setCancelable(boolean)
+         * @see #setOnDismissListener(android.content.DialogInterface.OnDismissListener)
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setOnCancelListener(OnCancelListener onCancelListener) {
+            P.mOnCancelListener = onCancelListener;
+            return this;
+        }
+
+        /**
+         * Sets the callback that will be called when the dialog is dismissed for any reason.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setOnDismissListener(OnDismissListener onDismissListener) {
+            P.mOnDismissListener = onDismissListener;
+            return this;
+        }
+
+        /**
+         * Sets the callback that will be called if a key is dispatched to the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setOnKeyListener(OnKeyListener onKeyListener) {
+            P.mOnKeyListener = onKeyListener;
+            return this;
+        }
+
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of the
+         * selected item via the supplied listener. This should be an array type i.e. R.array.foo
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setItems(@ArrayRes int itemsId, final OnClickListener listener) {
+            P.mItems = P.mContext.getResources().getTextArray(itemsId);
+            P.mOnClickListener = listener;
+            return this;
+        }
+
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of the
+         * selected item via the supplied listener.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setItems(CharSequence[] items, final OnClickListener listener) {
+            P.mItems = items;
+            P.mOnClickListener = listener;
+            return this;
+        }
+
+        /**
+         * Set a list of items, which are supplied by the given {@link ListAdapter}, to be
+         * displayed in the dialog as the content, you will be notified of the
+         * selected item via the supplied listener.
+         *
+         * @param adapter The {@link ListAdapter} to supply the list of items
+         * @param listener The listener that will be called when an item is clicked.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setAdapter(final ListAdapter adapter, final OnClickListener listener) {
+            P.mAdapter = adapter;
+            P.mOnClickListener = listener;
+            return this;
+        }
+
+        /**
+         * Set a list of items, which are supplied by the given {@link Cursor}, to be
+         * displayed in the dialog as the content, you will be notified of the
+         * selected item via the supplied listener.
+         *
+         * @param cursor The {@link Cursor} to supply the list of items
+         * @param listener The listener that will be called when an item is clicked.
+         * @param labelColumn The column name on the cursor containing the string to display
+         *          in the label.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setCursor(final Cursor cursor, final OnClickListener listener,
+                String labelColumn) {
+            P.mCursor = cursor;
+            P.mLabelColumn = labelColumn;
+            P.mOnClickListener = listener;
+            return this;
+        }
+
+        /**
+         * Set a list of items to be displayed in the dialog as the content,
+         * you will be notified of the selected item via the supplied listener.
+         * This should be an array type, e.g. R.array.foo. The list will have
+         * a check mark displayed to the right of the text for each checked
+         * item. Clicking on an item in the list will not dismiss the dialog.
+         * Clicking on a button will dismiss the dialog.
+         *
+         * @param itemsId the resource id of an array i.e. R.array.foo
+         * @param checkedItems specifies which items are checked. It should be null in which case no
+         *        items are checked. If non null it must be exactly the same length as the array of
+         *        items.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems,
+                final OnMultiChoiceClickListener listener) {
+            P.mItems = P.mContext.getResources().getTextArray(itemsId);
+            P.mOnCheckboxClickListener = listener;
+            P.mCheckedItems = checkedItems;
+            P.mIsMultiChoice = true;
+            return this;
+        }
+
+        /**
+         * Set a list of items to be displayed in the dialog as the content,
+         * you will be notified of the selected item via the supplied listener.
+         * The list will have a check mark displayed to the right of the text
+         * for each checked item. Clicking on an item in the list will not
+         * dismiss the dialog. Clicking on a button will dismiss the dialog.
+         *
+         * @param items the text of the items to be displayed in the list.
+         * @param checkedItems specifies which items are checked. It should be null in which case no
+         *        items are checked. If non null it must be exactly the same length as the array of
+         *        items.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems,
+                final OnMultiChoiceClickListener listener) {
+            P.mItems = items;
+            P.mOnCheckboxClickListener = listener;
+            P.mCheckedItems = checkedItems;
+            P.mIsMultiChoice = true;
+            return this;
+        }
+
+        /**
+         * Set a list of items to be displayed in the dialog as the content,
+         * you will be notified of the selected item via the supplied listener.
+         * The list will have a check mark displayed to the right of the text
+         * for each checked item. Clicking on an item in the list will not
+         * dismiss the dialog. Clicking on a button will dismiss the dialog.
+         *
+         * @param cursor the cursor used to provide the items.
+         * @param isCheckedColumn specifies the column name on the cursor to use to determine
+         *        whether a checkbox is checked or not. It must return an integer value where 1
+         *        means checked and 0 means unchecked.
+         * @param labelColumn The column name on the cursor containing the string to display in the
+         *        label.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn,
+                final OnMultiChoiceClickListener listener) {
+            P.mCursor = cursor;
+            P.mOnCheckboxClickListener = listener;
+            P.mIsCheckedColumn = isCheckedColumn;
+            P.mLabelColumn = labelColumn;
+            P.mIsMultiChoice = true;
+            return this;
+        }
+
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of
+         * the selected item via the supplied listener. This should be an array type i.e.
+         * R.array.foo The list will have a check mark displayed to the right of the text for the
+         * checked item. Clicking on an item in the list will not dismiss the dialog. Clicking on a
+         * button will dismiss the dialog.
+         *
+         * @param itemsId the resource id of an array i.e. R.array.foo
+         * @param checkedItem specifies which item is checked. If -1 no items are checked.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem,
+                final OnClickListener listener) {
+            P.mItems = P.mContext.getResources().getTextArray(itemsId);
+            P.mOnClickListener = listener;
+            P.mCheckedItem = checkedItem;
+            P.mIsSingleChoice = true;
+            return this;
+        }
+
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of
+         * the selected item via the supplied listener. The list will have a check mark displayed to
+         * the right of the text for the checked item. Clicking on an item in the list will not
+         * dismiss the dialog. Clicking on a button will dismiss the dialog.
+         *
+         * @param cursor the cursor to retrieve the items from.
+         * @param checkedItem specifies which item is checked. If -1 no items are checked.
+         * @param labelColumn The column name on the cursor containing the string to display in the
+         *        label.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn,
+                final OnClickListener listener) {
+            P.mCursor = cursor;
+            P.mOnClickListener = listener;
+            P.mCheckedItem = checkedItem;
+            P.mLabelColumn = labelColumn;
+            P.mIsSingleChoice = true;
+            return this;
+        }
+
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of
+         * the selected item via the supplied listener. The list will have a check mark displayed to
+         * the right of the text for the checked item. Clicking on an item in the list will not
+         * dismiss the dialog. Clicking on a button will dismiss the dialog.
+         *
+         * @param items the items to be displayed.
+         * @param checkedItem specifies which item is checked. If -1 no items are checked.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final OnClickListener listener) {
+            P.mItems = items;
+            P.mOnClickListener = listener;
+            P.mCheckedItem = checkedItem;
+            P.mIsSingleChoice = true;
+            return this;
+        }
+
+        /**
+         * Set a list of items to be displayed in the dialog as the content, you will be notified of
+         * the selected item via the supplied listener. The list will have a check mark displayed to
+         * the right of the text for the checked item. Clicking on an item in the list will not
+         * dismiss the dialog. Clicking on a button will dismiss the dialog.
+         *
+         * @param adapter The {@link ListAdapter} to supply the list of items
+         * @param checkedItem specifies which item is checked. If -1 no items are checked.
+         * @param listener notified when an item on the list is clicked. The dialog will not be
+         *        dismissed when an item is clicked. It will only be dismissed if clicked on a
+         *        button, if no buttons are supplied it's up to the user to dismiss the dialog.
+         *
+         * @return This Builder object to allow for chaining of calls to set methods
+         */
+        public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final OnClickListener listener) {
+            P.mAdapter = adapter;
+            P.mOnClickListener = listener;
+            P.mCheckedItem = checkedItem;
+            P.mIsSingleChoice = true;
+            return this;
+        }
+
+        /**
+         * Sets a listener to be invoked when an item in the list is selected.
+         *
+         * @param listener the listener to be invoked
+         * @return this Builder object to allow for chaining of calls to set methods
+         * @see AdapterView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
+         */
+        public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) {
+            P.mOnItemSelectedListener = listener;
+            return this;
+        }
+
+        /**
+         * Set a custom view resource to be the contents of the Dialog. The
+         * resource will be inflated, adding all top-level views to the screen.
+         *
+         * @param layoutResId Resource ID to be inflated.
+         * @return this Builder object to allow for chaining of calls to set
+         *         methods
+         */
+        public Builder setView(int layoutResId) {
+            P.mView = null;
+            P.mViewLayoutResId = layoutResId;
+            P.mViewSpacingSpecified = false;
+            return this;
+        }
+
+        /**
+         * Sets a custom view to be the contents of the alert dialog.
+         * <p>
+         * When using a pre-Holo theme, if the supplied view is an instance of
+         * a {@link ListView} then the light background will be used.
+         * <p>
+         * <strong>Note:</strong> To ensure consistent styling, the custom view
+         * should be inflated or constructed using the alert dialog's themed
+         * context obtained via {@link #getContext()}.
+         *
+         * @param view the view to use as the contents of the alert dialog
+         * @return this Builder object to allow for chaining of calls to set
+         *         methods
+         */
+        public Builder setView(View view) {
+            P.mView = view;
+            P.mViewLayoutResId = 0;
+            P.mViewSpacingSpecified = false;
+            return this;
+        }
+
+        /**
+         * Sets a custom view to be the contents of the alert dialog and
+         * specifies additional padding around that view.
+         * <p>
+         * When using a pre-Holo theme, if the supplied view is an instance of
+         * a {@link ListView} then the light background will be used.
+         * <p>
+         * <strong>Note:</strong> To ensure consistent styling, the custom view
+         * should be inflated or constructed using the alert dialog's themed
+         * context obtained via {@link #getContext()}.
+         *
+         * @param view the view to use as the contents of the alert dialog
+         * @param viewSpacingLeft spacing between the left edge of the view and
+         *                        the dialog frame
+         * @param viewSpacingTop spacing between the top edge of the view and
+         *                       the dialog frame
+         * @param viewSpacingRight spacing between the right edge of the view
+         *                         and the dialog frame
+         * @param viewSpacingBottom spacing between the bottom edge of the view
+         *                          and the dialog frame
+         * @return this Builder object to allow for chaining of calls to set
+         *         methods
+         *
+         * @hide Remove once the framework usages have been replaced.
+         * @deprecated Set the padding on the view itself.
+         */
+        @Deprecated
+        @UnsupportedAppUsage
+        public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop,
+                int viewSpacingRight, int viewSpacingBottom) {
+            P.mView = view;
+            P.mViewLayoutResId = 0;
+            P.mViewSpacingSpecified = true;
+            P.mViewSpacingLeft = viewSpacingLeft;
+            P.mViewSpacingTop = viewSpacingTop;
+            P.mViewSpacingRight = viewSpacingRight;
+            P.mViewSpacingBottom = viewSpacingBottom;
+            return this;
+        }
+
+        /**
+         * Sets the alert dialog to use the inverse background, regardless of
+         * what the contents is.
+         *
+         * @param useInverseBackground whether to use the inverse background
+         * @return this Builder object to allow for chaining of calls to set methods
+         * @deprecated This flag is only used for pre-Material themes. Instead,
+         *             specify the window background using on the alert dialog
+         *             theme.
+         */
+        @Deprecated
+        public Builder setInverseBackgroundForced(boolean useInverseBackground) {
+            P.mForceInverseBackground = useInverseBackground;
+            return this;
+        }
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public Builder setRecycleOnMeasureEnabled(boolean enabled) {
+            P.mRecycleOnMeasure = enabled;
+            return this;
+        }
+
+
+        /**
+         * Creates an {@link AlertDialog} with the arguments supplied to this
+         * builder.
+         * <p>
+         * Calling this method does not display the dialog. If no additional
+         * processing is needed, {@link #show()} may be called instead to both
+         * create and display the dialog.
+         */
+        public AlertDialog create() {
+            // Context has already been wrapped with the appropriate theme.
+            final AlertDialog dialog = new AlertDialog(P.mContext, 0, false);
+            P.apply(dialog.mAlert);
+            dialog.setCancelable(P.mCancelable);
+            if (P.mCancelable) {
+                dialog.setCanceledOnTouchOutside(true);
+            }
+            dialog.setOnCancelListener(P.mOnCancelListener);
+            dialog.setOnDismissListener(P.mOnDismissListener);
+            if (P.mOnKeyListener != null) {
+                dialog.setOnKeyListener(P.mOnKeyListener);
+            }
+            return dialog;
+        }
+
+        /**
+         * Creates an {@link AlertDialog} with the arguments supplied to this
+         * builder and immediately displays the dialog.
+         * <p>
+         * Calling this method is functionally identical to:
+         * <pre>
+         *     AlertDialog dialog = builder.create();
+         *     dialog.show();
+         * </pre>
+         */
+        public AlertDialog show() {
+            final AlertDialog dialog = create();
+            dialog.show();
+            return dialog;
+        }
+    }
+
+}
diff --git a/android/app/AliasActivity.java b/android/app/AliasActivity.java
new file mode 100644
index 0000000..3756529
--- /dev/null
+++ b/android/app/AliasActivity.java
@@ -0,0 +1,124 @@
+/*
+ * 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.app;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.XmlResourceParser;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import com.android.internal.util.XmlUtils;
+
+import java.io.IOException;
+
+/**
+ * Stub activity that launches another activity (and then finishes itself)
+ * based on information in its component's manifest meta-data.  This is a
+ * simple way to implement an alias-like mechanism.
+ * 
+ * To use this activity, you should include in the manifest for the associated
+ * component an entry named "android.app.alias".  It is a reference to an XML
+ * resource describing an intent that launches the real application.
+ */
+public class AliasActivity extends Activity {
+    /**
+     * This is the name under which you should store in your component the
+     * meta-data information about the alias.  It is a reference to an XML
+     * resource describing an intent that launches the real application.
+     * {@hide}
+     */
+    public final String ALIAS_META_DATA = "android.app.alias";
+    
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        
+        XmlResourceParser parser = null;
+        try {
+            ActivityInfo ai = getPackageManager().getActivityInfo(
+                    getComponentName(), PackageManager.GET_META_DATA);
+            parser = ai.loadXmlMetaData(getPackageManager(),
+                    ALIAS_META_DATA);
+            if (parser == null) {
+                throw new RuntimeException("Alias requires a meta-data field "
+                        + ALIAS_META_DATA);
+            }
+            
+            Intent intent = parseAlias(parser);
+            if (intent == null) {
+                throw new RuntimeException(
+                        "No <intent> tag found in alias description");
+            }
+            
+            startActivity(intent);
+            finish();
+            
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new RuntimeException("Error parsing alias", e);
+        } catch (XmlPullParserException e) {
+            throw new RuntimeException("Error parsing alias", e);
+        } catch (IOException e) {
+            throw new RuntimeException("Error parsing alias", e);
+        } finally {
+            if (parser != null) parser.close();
+        }
+    }
+
+    private Intent parseAlias(XmlPullParser parser)
+            throws XmlPullParserException, IOException {
+        AttributeSet attrs = Xml.asAttributeSet(parser);
+        
+        Intent intent = null;
+        
+        int type;
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+                && type != XmlPullParser.START_TAG) {
+        }
+        
+        String nodeName = parser.getName();
+        if (!"alias".equals(nodeName)) {
+            throw new RuntimeException(
+                    "Alias meta-data must start with <alias> tag; found"
+                    + nodeName + " at " + parser.getPositionDescription());
+        }
+        
+        int outerDepth = parser.getDepth();
+        while ((type=parser.next()) != XmlPullParser.END_DOCUMENT
+               && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {
+            if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) {
+                continue;
+            }
+
+            nodeName = parser.getName();
+            if ("intent".equals(nodeName)) {
+                Intent gotIntent = Intent.parseIntent(getResources(), parser, attrs);
+                if (intent == null) intent = gotIntent;
+            } else {
+                XmlUtils.skipCurrentTag(parser);
+            }
+        }
+        
+        return intent;
+    }
+    
+}
diff --git a/android/app/AppComponentFactory.java b/android/app/AppComponentFactory.java
new file mode 100644
index 0000000..5b02817
--- /dev/null
+++ b/android/app/AppComponentFactory.java
@@ -0,0 +1,154 @@
+/*
+ * 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.BroadcastReceiver;
+import android.content.ContentProvider;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+
+/**
+ * Interface used to control the instantiation of manifest elements.
+ *
+ * @see #instantiateApplication
+ * @see #instantiateActivity
+ * @see #instantiateClassLoader
+ * @see #instantiateService
+ * @see #instantiateReceiver
+ * @see #instantiateProvider
+ */
+public class AppComponentFactory {
+
+    /**
+     * Selects the class loader which will be used by the platform to instantiate app components.
+     * <p>
+     * The default implementation of this method returns the {@code cl} parameter unchanged.
+     * Applications can override this method to set up a custom class loader or a custom class
+     * loader hierarchy and return it to the platform.
+     * <p>
+     * The method is a hook invoked before any application components are instantiated or the
+     * application Context is initialized. It is intended to allow the application's classes to
+     * be loaded from a different source than the base/split APK(s).
+     * <p>
+     * The default class loader {@code cl} is created by the platform and used to load the
+     * application's base or split APK(s). Its parent is typically the boot class loader, unless
+     * running under instrumentation. Its classname is configurable using the
+     * {@link android.R.attr#classLoader} manifest attribute.
+     *
+     * @param cl        The default class loader created by the platform.
+     * @param aInfo     Information about the application being loaded.
+     */
+    public @NonNull ClassLoader instantiateClassLoader(@NonNull ClassLoader cl,
+            @NonNull ApplicationInfo aInfo) {
+        return cl;
+    }
+
+    /**
+     * Allows application to override the creation of the application object. This can be used to
+     * perform things such as dependency injection or class loader changes to these
+     * classes.
+     * <p>
+     * This method is only intended to provide a hook for instantiation. It does not provide
+     * earlier access to the Application object. The returned object will not be initialized
+     * as a Context yet and should not be used to interact with other android APIs.
+     *
+     * @param cl        The default classloader to use for instantiation.
+     * @param className The class to be instantiated.
+     */
+    public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
+            @NonNull String className)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        return (Application) cl.loadClass(className).newInstance();
+    }
+
+    /**
+     * Allows application to override the creation of activities. This can be used to
+     * perform things such as dependency injection or class loader changes to these
+     * classes.
+     * <p>
+     * This method is only intended to provide a hook for instantiation. It does not provide
+     * earlier access to the Activity object. The returned object will not be initialized
+     * as a Context yet and should not be used to interact with other android APIs.
+     *
+     * @param cl        The default classloader to use for instantiation.
+     * @param className The class to be instantiated.
+     * @param intent    Intent creating the class.
+     */
+    public @NonNull Activity instantiateActivity(@NonNull ClassLoader cl, @NonNull String className,
+            @Nullable Intent intent)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        return (Activity) cl.loadClass(className).newInstance();
+    }
+
+    /**
+     * Allows application to override the creation of receivers. This can be used to
+     * perform things such as dependency injection or class loader changes to these
+     * classes.
+     *
+     * @param cl        The default classloader to use for instantiation.
+     * @param className The class to be instantiated.
+     * @param intent    Intent creating the class.
+     */
+    public @NonNull BroadcastReceiver instantiateReceiver(@NonNull ClassLoader cl,
+            @NonNull String className, @Nullable Intent intent)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        return (BroadcastReceiver) cl.loadClass(className).newInstance();
+    }
+
+    /**
+     * Allows application to override the creation of services. This can be used to
+     * perform things such as dependency injection or class loader changes to these
+     * classes.
+     * <p>
+     * This method is only intended to provide a hook for instantiation. It does not provide
+     * earlier access to the Service object. The returned object will not be initialized
+     * as a Context yet and should not be used to interact with other android APIs.
+     *
+     * @param cl        The default classloader to use for instantiation.
+     * @param className The class to be instantiated.
+     * @param intent    Intent creating the class.
+     */
+    public @NonNull Service instantiateService(@NonNull ClassLoader cl,
+            @NonNull String className, @Nullable Intent intent)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        return (Service) cl.loadClass(className).newInstance();
+    }
+
+    /**
+     * Allows application to override the creation of providers. This can be used to
+     * perform things such as dependency injection or class loader changes to these
+     * classes.
+     * <p>
+     * This method is only intended to provide a hook for instantiation. It does not provide
+     * earlier access to the ContentProvider object. The returned object will not be initialized
+     * with a Context yet and should not be used to interact with other android APIs.
+     *
+     * @param cl        The default classloader to use for instantiation.
+     * @param className The class to be instantiated.
+     */
+    public @NonNull ContentProvider instantiateProvider(@NonNull ClassLoader cl,
+            @NonNull String className)
+            throws InstantiationException, IllegalAccessException, ClassNotFoundException {
+        return (ContentProvider) cl.loadClass(className).newInstance();
+    }
+
+    /**
+     * @hide
+     */
+    public static final AppComponentFactory DEFAULT = new AppComponentFactory();
+}
diff --git a/android/app/AppDetailsActivity.java b/android/app/AppDetailsActivity.java
new file mode 100644
index 0000000..b71af88
--- /dev/null
+++ b/android/app/AppDetailsActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.TestApi;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * Helper activity that forwards you to app details page.
+ *
+ * @hide
+ */
+@TestApi
+public class AppDetailsActivity extends Activity {
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Intent intent = new Intent(android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+        intent.setData(android.net.Uri.fromParts("package", getPackageName(), null));
+        startActivity(intent);
+        finish();
+    }
+}
diff --git a/android/app/AppGlobals.java b/android/app/AppGlobals.java
new file mode 100644
index 0000000..1f737b6
--- /dev/null
+++ b/android/app/AppGlobals.java
@@ -0,0 +1,69 @@
+/*
+ * 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.app;
+
+import android.annotation.UnsupportedAppUsage;
+import android.content.pm.IPackageManager;
+
+/**
+ * Special private access for certain globals related to a process.
+ * @hide
+ */
+public class AppGlobals {
+    /**
+     * Return the first Application object made in the process.
+     * NOTE: Only works on the main thread.
+     */
+    @UnsupportedAppUsage
+    public static Application getInitialApplication() {
+        return ActivityThread.currentApplication();
+    }
+    
+    /**
+     * Return the package name of the first .apk loaded into the process.
+     * NOTE: Only works on the main thread.
+     */
+    @UnsupportedAppUsage
+    public static String getInitialPackage() {
+        return ActivityThread.currentPackageName();
+    }
+
+    /**
+     * Return the raw interface to the package manager.
+     * @return The package manager.
+     */
+    @UnsupportedAppUsage
+    public static IPackageManager getPackageManager() {
+        return ActivityThread.getPackageManager();
+    }
+
+    /**
+     * Gets the value of an integer core setting.
+     *
+     * @param key The setting key.
+     * @param defaultValue The setting default value.
+     * @return The core settings.
+     */
+    public static int getIntCoreSetting(String key, int defaultValue) {
+        ActivityThread currentActivityThread = ActivityThread.currentActivityThread();
+        if (currentActivityThread != null) {
+            return currentActivityThread.getIntCoreSetting(key, defaultValue);
+        } else {
+            return defaultValue;
+        }
+    }
+}
diff --git a/android/app/AppOpsManager.java b/android/app/AppOpsManager.java
new file mode 100644
index 0000000..fb72e65
--- /dev/null
+++ b/android/app/AppOpsManager.java
@@ -0,0 +1,5765 @@
+/*
+ * 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.app;
+
+import android.Manifest;
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SystemApi;
+import android.annotation.SystemService;
+import android.annotation.TestApi;
+import android.annotation.UnsupportedAppUsage;
+import android.app.usage.UsageStatsManager;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.media.AudioAttributes.AttributeUsage;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.UserManager;
+import android.util.ArrayMap;
+import android.util.LongSparseArray;
+import android.util.LongSparseLongArray;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.Immutable;
+import com.android.internal.app.IAppOpsActiveCallback;
+import com.android.internal.app.IAppOpsCallback;
+import com.android.internal.app.IAppOpsNotedCallback;
+import com.android.internal.app.IAppOpsService;
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * API for interacting with "application operation" tracking.
+ *
+ * <p>This API is not generally intended for third party application developers; most
+ * features are only available to system applications.
+ */
+@SystemService(Context.APP_OPS_SERVICE)
+public class AppOpsManager {
+    /**
+     * <p>App ops allows callers to:</p>
+     *
+     * <ul>
+     * <li> Note when operations are happening, and find out if they are allowed for the current
+     * caller.</li>
+     * <li> Disallow specific apps from doing specific operations.</li>
+     * <li> Collect all of the current information about operations that have been executed or
+     * are not being allowed.</li>
+     * <li> Monitor for changes in whether an operation is allowed.</li>
+     * </ul>
+     *
+     * <p>Each operation is identified by a single integer; these integers are a fixed set of
+     * operations, enumerated by the OP_* constants.
+     *
+     * <p></p>When checking operations, the result is a "mode" integer indicating the current
+     * setting for the operation under that caller: MODE_ALLOWED, MODE_IGNORED (don't execute
+     * the operation but fake its behavior enough so that the caller doesn't crash),
+     * MODE_ERRORED (throw a SecurityException back to the caller; the normal operation calls
+     * will do this for you).
+     */
+
+    final Context mContext;
+
+    @UnsupportedAppUsage
+    final IAppOpsService mService;
+
+    @GuardedBy("mModeWatchers")
+    private final ArrayMap<OnOpChangedListener, IAppOpsCallback> mModeWatchers =
+            new ArrayMap<>();
+
+    @GuardedBy("mActiveWatchers")
+    private final ArrayMap<OnOpActiveChangedListener, IAppOpsActiveCallback> mActiveWatchers =
+            new ArrayMap<>();
+
+    @GuardedBy("mNotedWatchers")
+    private final ArrayMap<OnOpNotedListener, IAppOpsNotedCallback> mNotedWatchers =
+            new ArrayMap<>();
+
+    static IBinder sToken;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "HISTORICAL_MODE_" }, value = {
+            HISTORICAL_MODE_DISABLED,
+            HISTORICAL_MODE_ENABLED_ACTIVE,
+            HISTORICAL_MODE_ENABLED_PASSIVE
+    })
+    public @interface HistoricalMode {}
+
+    /**
+     * Mode in which app op history is completely disabled.
+     * @hide
+     */
+    @TestApi
+    public static final int HISTORICAL_MODE_DISABLED = 0;
+
+    /**
+     * Mode in which app op history is enabled and app ops performed by apps would
+     * be tracked. This is the mode in which the feature is completely enabled.
+     * @hide
+     */
+    @TestApi
+    public static final int HISTORICAL_MODE_ENABLED_ACTIVE = 1;
+
+    /**
+     * Mode in which app op history is enabled but app ops performed by apps would
+     * not be tracked and the only way to add ops to the history is via explicit calls
+     * to dedicated APIs. This mode is useful for testing to allow full control of
+     * the historical content.
+     * @hide
+     */
+    @TestApi
+    public static final int HISTORICAL_MODE_ENABLED_PASSIVE = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "MODE_" }, value = {
+            MODE_ALLOWED,
+            MODE_IGNORED,
+            MODE_ERRORED,
+            MODE_DEFAULT,
+            MODE_FOREGROUND
+    })
+    public @interface Mode {}
+
+    /**
+     * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is
+     * allowed to perform the given operation.
+     */
+    public static final int MODE_ALLOWED = 0;
+
+    /**
+     * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller is
+     * not allowed to perform the given operation, and this attempt should
+     * <em>silently fail</em> (it should not cause the app to crash).
+     */
+    public static final int MODE_IGNORED = 1;
+
+    /**
+     * Result from {@link #checkOpNoThrow}, {@link #noteOpNoThrow}, {@link #startOpNoThrow}: the
+     * given caller is not allowed to perform the given operation, and this attempt should
+     * cause it to have a fatal error, typically a {@link SecurityException}.
+     */
+    public static final int MODE_ERRORED = 2;
+
+    /**
+     * Result from {@link #checkOp}, {@link #noteOp}, {@link #startOp}: the given caller should
+     * use its default security check.  This mode is not normally used; it should only be used
+     * with appop permissions, and callers must explicitly check for it and deal with it.
+     */
+    public static final int MODE_DEFAULT = 3;
+
+    /**
+     * Special mode that means "allow only when app is in foreground."  This is <b>not</b>
+     * returned from {@link #unsafeCheckOp}, {@link #noteOp}, {@link #startOp}.  Rather,
+     * {@link #unsafeCheckOp} will always return {@link #MODE_ALLOWED} (because it is always
+     * possible for it to be ultimately allowed, depending on the app's background state),
+     * and {@link #noteOp} and {@link #startOp} will return {@link #MODE_ALLOWED} when the app
+     * being checked is currently in the foreground, otherwise {@link #MODE_IGNORED}.
+     *
+     * <p>The only place you will this normally see this value is through
+     * {@link #unsafeCheckOpRaw}, which returns the actual raw mode of the op.  Note that because
+     * you can't know the current state of the app being checked (and it can change at any
+     * point), you can only treat the result here as an indication that it will vary between
+     * {@link #MODE_ALLOWED} and {@link #MODE_IGNORED} depending on changes in the background
+     * state of the app.  You thus must always use {@link #noteOp} or {@link #startOp} to do
+     * the actual check for access to the op.</p>
+     */
+    public static final int MODE_FOREGROUND = 4;
+
+    /**
+     * Flag for {@link #startWatchingMode(String, String, int, OnOpChangedListener)}:
+     * Also get reports if the foreground state of an op's uid changes.  This only works
+     * when watching a particular op, not when watching a package.
+     */
+    public static final int WATCH_FOREGROUND_CHANGES = 1 << 0;
+
+    /**
+     * @hide
+     */
+    public static final String[] MODE_NAMES = new String[] {
+            "allow",        // MODE_ALLOWED
+            "ignore",       // MODE_IGNORED
+            "deny",         // MODE_ERRORED
+            "default",      // MODE_DEFAULT
+            "foreground",   // MODE_FOREGROUND
+    };
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "UID_STATE_" }, value = {
+            UID_STATE_PERSISTENT,
+            UID_STATE_TOP,
+            UID_STATE_FOREGROUND_SERVICE_LOCATION,
+            UID_STATE_FOREGROUND_SERVICE,
+            UID_STATE_FOREGROUND,
+            UID_STATE_BACKGROUND,
+            UID_STATE_CACHED
+    })
+    public @interface UidState {}
+
+    /**
+     * Uid state: The UID is a foreground persistent app. The lower the UID
+     * state the more important the UID is for the user.
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int UID_STATE_PERSISTENT = 100;
+
+    /**
+     * Uid state: The UID is top foreground app. The lower the UID
+     * state the more important the UID is for the user.
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int UID_STATE_TOP = 200;
+
+    /**
+     * Uid state: The UID is running a foreground service of location type.
+     * The lower the UID state the more important the UID is for the user.
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int UID_STATE_FOREGROUND_SERVICE_LOCATION = 300;
+
+    /**
+     * Uid state: The UID is running a foreground service. The lower the UID
+     * state the more important the UID is for the user.
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int UID_STATE_FOREGROUND_SERVICE = 400;
+
+    /**
+     * The max, which is min priority, UID state for which any app op
+     * would be considered as performed in the foreground.
+     * @hide
+     */
+    public static final int UID_STATE_MAX_LAST_NON_RESTRICTED = UID_STATE_FOREGROUND_SERVICE;
+
+    /**
+     * Uid state: The UID is a foreground app. The lower the UID
+     * state the more important the UID is for the user.
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int UID_STATE_FOREGROUND = 500;
+
+    /**
+     * Uid state: The UID is a background app. The lower the UID
+     * state the more important the UID is for the user.
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int UID_STATE_BACKGROUND = 600;
+
+    /**
+     * Uid state: The UID is a cached app. The lower the UID
+     * state the more important the UID is for the user.
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int UID_STATE_CACHED = 700;
+
+    /**
+     * Uid state: The UID state with the highest priority.
+     * @hide
+     */
+    public static final int MAX_PRIORITY_UID_STATE = UID_STATE_PERSISTENT;
+
+    /**
+     * Uid state: The UID state with the lowest priority.
+     * @hide
+     */
+    public static final int MIN_PRIORITY_UID_STATE = UID_STATE_CACHED;
+
+    /**
+     * Resolves the first unrestricted state given an app op. Location is
+     * special as we want to allow its access only if a dedicated location
+     * foreground service is running. For other ops we consider any foreground
+     * service as a foreground state.
+     *
+     * @param op The op to resolve.
+     * @return The last restricted UID state.
+     *
+     * @hide
+     */
+    public static int resolveFirstUnrestrictedUidState(int op) {
+        switch (op) {
+            case OP_FINE_LOCATION:
+            case OP_COARSE_LOCATION:
+            case OP_MONITOR_LOCATION:
+            case OP_MONITOR_HIGH_POWER_LOCATION: {
+                return UID_STATE_FOREGROUND_SERVICE_LOCATION;
+            }
+        }
+        return UID_STATE_FOREGROUND_SERVICE;
+    }
+
+    /**
+     * Resolves the last restricted state given an app op. Location is
+     * special as we want to allow its access only if a dedicated location
+     * foreground service is running. For other ops we consider any foreground
+     * service as a foreground state.
+     *
+     * @param op The op to resolve.
+     * @return The last restricted UID state.
+     *
+     * @hide
+     */
+    public static int resolveLastRestrictedUidState(int op) {
+        switch (op) {
+            case OP_FINE_LOCATION:
+            case OP_COARSE_LOCATION: {
+                return UID_STATE_FOREGROUND_SERVICE;
+            }
+        }
+        return UID_STATE_FOREGROUND;
+    }
+
+    /** @hide Note: Keep these sorted */
+    public static final int[] UID_STATES = {
+            UID_STATE_PERSISTENT,
+            UID_STATE_TOP,
+            UID_STATE_FOREGROUND_SERVICE_LOCATION,
+            UID_STATE_FOREGROUND_SERVICE,
+            UID_STATE_FOREGROUND,
+            UID_STATE_BACKGROUND,
+            UID_STATE_CACHED
+    };
+
+    /** @hide */
+    public static String getUidStateName(@UidState int uidState) {
+        switch (uidState) {
+            case UID_STATE_PERSISTENT:
+                return "pers";
+            case UID_STATE_TOP:
+                return "top";
+            case UID_STATE_FOREGROUND_SERVICE_LOCATION:
+                return "fgsvcl";
+            case UID_STATE_FOREGROUND_SERVICE:
+                return "fgsvc";
+            case UID_STATE_FOREGROUND:
+                return "fg";
+            case UID_STATE_BACKGROUND:
+                return "bg";
+            case UID_STATE_CACHED:
+                return "cch";
+            default:
+                return "unknown";
+        }
+    }
+
+    /**
+     * Flag: non proxy operations. These are operations
+     * performed on behalf of the app itself and not on behalf of
+     * another one.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int OP_FLAG_SELF = 0x1;
+
+    /**
+     * Flag: trusted proxy operations. These are operations
+     * performed on behalf of another app by a trusted app.
+     * Which is work a trusted app blames on another app.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int OP_FLAG_TRUSTED_PROXY = 0x2;
+
+    /**
+     * Flag: untrusted proxy operations. These are operations
+     * performed on behalf of another app by an untrusted app.
+     * Which is work an untrusted app blames on another app.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int OP_FLAG_UNTRUSTED_PROXY = 0x4;
+
+    /**
+     * Flag: trusted proxied operations. These are operations
+     * performed by a trusted other app on behalf of an app.
+     * Which is work an app was blamed for by a trusted app.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int OP_FLAG_TRUSTED_PROXIED = 0x8;
+
+    /**
+     * Flag: untrusted proxied operations. These are operations
+     * performed by an untrusted other app on behalf of an app.
+     * Which is work an app was blamed for by an untrusted app.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int OP_FLAG_UNTRUSTED_PROXIED = 0x10;
+
+    /**
+     * Flags: all operations. These include operations matched
+     * by {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXIED},
+     * {@link #OP_FLAG_UNTRUSTED_PROXIED}, {@link #OP_FLAG_TRUSTED_PROXIED},
+     * {@link #OP_FLAG_UNTRUSTED_PROXIED}.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final int OP_FLAGS_ALL =
+            OP_FLAG_SELF
+                | OP_FLAG_TRUSTED_PROXY
+                | OP_FLAG_UNTRUSTED_PROXY
+                | OP_FLAG_TRUSTED_PROXIED
+                | OP_FLAG_UNTRUSTED_PROXIED;
+
+    /**
+     * Flags: all trusted operations which is ones either the app did {@link #OP_FLAG_SELF},
+     * or it was blamed for by a trusted app {@link #OP_FLAG_TRUSTED_PROXIED}, or ones the
+     * app if untrusted blamed on other apps {@link #OP_FLAG_UNTRUSTED_PROXY}.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int OP_FLAGS_ALL_TRUSTED = AppOpsManager.OP_FLAG_SELF
+        | AppOpsManager.OP_FLAG_UNTRUSTED_PROXY
+        | AppOpsManager.OP_FLAG_TRUSTED_PROXIED;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, prefix = { "FLAG_" }, value = {
+            OP_FLAG_SELF,
+            OP_FLAG_TRUSTED_PROXY,
+            OP_FLAG_UNTRUSTED_PROXY,
+            OP_FLAG_TRUSTED_PROXIED,
+            OP_FLAG_UNTRUSTED_PROXIED
+    })
+    public @interface OpFlags {}
+
+
+    /** @hide */
+    public static final String getFlagName(@OpFlags int flag) {
+        switch (flag) {
+            case OP_FLAG_SELF:
+                return "s";
+            case OP_FLAG_TRUSTED_PROXY:
+                return "tp";
+            case OP_FLAG_UNTRUSTED_PROXY:
+                return "up";
+            case OP_FLAG_TRUSTED_PROXIED:
+                return "tpd";
+            case OP_FLAG_UNTRUSTED_PROXIED:
+                return "upd";
+            default:
+                return "unknown";
+        }
+    }
+
+    private static final int UID_STATE_OFFSET = 31;
+    private static final int FLAGS_MASK = 0xFFFFFFFF;
+
+    /**
+     * Key for a data bucket storing app op state. The bucket
+     * is composed of the uid state and state flags. This way
+     * we can query data for given uid state and a set of flags where
+     * the flags control which type of data to get. For example,
+     * one can get the ops an app did on behalf of other apps
+     * while in the background.
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @Target({ElementType.METHOD, ElementType.PARAMETER, ElementType.FIELD})
+    public @interface DataBucketKey {
+    }
+
+    /** @hide */
+    public static String keyToString(@DataBucketKey long key) {
+        final int uidState = extractUidStateFromKey(key);
+        final int flags = extractFlagsFromKey(key);
+        return "[" + getUidStateName(uidState) + "-" + flagsToString(flags) + "]";
+    }
+
+    /** @hide */
+    public static @DataBucketKey long makeKey(@UidState int uidState, @OpFlags int flags) {
+        return ((long) uidState << UID_STATE_OFFSET) | flags;
+    }
+
+    /** @hide */
+    public static int extractUidStateFromKey(@DataBucketKey long key) {
+        return (int) (key >> UID_STATE_OFFSET);
+    }
+
+    /** @hide */
+    public static int extractFlagsFromKey(@DataBucketKey long key) {
+        return (int) (key & FLAGS_MASK);
+    }
+
+    /** @hide */
+    public static String flagsToString(@OpFlags int flags) {
+        final StringBuilder flagsBuilder = new StringBuilder();
+        while (flags != 0) {
+            final int flag = 1 << Integer.numberOfTrailingZeros(flags);
+            flags &= ~flag;
+            if (flagsBuilder.length() > 0) {
+                flagsBuilder.append('|');
+            }
+            flagsBuilder.append(getFlagName(flag));
+        }
+        return flagsBuilder.toString();
+    }
+
+    // when adding one of these:
+    //  - increment _NUM_OP
+    //  - define an OPSTR_* constant (marked as @SystemApi)
+    //  - add rows to sOpToSwitch, sOpToString, sOpNames, sOpToPerms, sOpDefault
+    //  - add descriptive strings to Settings/res/values/arrays.xml
+    //  - add the op to the appropriate template in AppOpsState.OpsTemplate (settings app)
+
+    /** @hide No operation specified. */
+    @UnsupportedAppUsage
+    public static final int OP_NONE = -1;
+    /** @hide Access to coarse location information. */
+    @TestApi
+    public static final int OP_COARSE_LOCATION = 0;
+    /** @hide Access to fine location information. */
+    @UnsupportedAppUsage
+    public static final int OP_FINE_LOCATION = 1;
+    /** @hide Causing GPS to run. */
+    @UnsupportedAppUsage
+    public static final int OP_GPS = 2;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_VIBRATE = 3;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_READ_CONTACTS = 4;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_WRITE_CONTACTS = 5;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_READ_CALL_LOG = 6;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_WRITE_CALL_LOG = 7;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_READ_CALENDAR = 8;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_WRITE_CALENDAR = 9;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_WIFI_SCAN = 10;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_POST_NOTIFICATION = 11;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_NEIGHBORING_CELLS = 12;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_CALL_PHONE = 13;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_READ_SMS = 14;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_WRITE_SMS = 15;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_RECEIVE_SMS = 16;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_RECEIVE_EMERGECY_SMS = 17;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_RECEIVE_MMS = 18;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_RECEIVE_WAP_PUSH = 19;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_SEND_SMS = 20;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_READ_ICC_SMS = 21;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_WRITE_ICC_SMS = 22;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_WRITE_SETTINGS = 23;
+    /** @hide Required to draw on top of other apps. */
+    @TestApi
+    public static final int OP_SYSTEM_ALERT_WINDOW = 24;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_ACCESS_NOTIFICATIONS = 25;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_CAMERA = 26;
+    /** @hide */
+    @TestApi
+    public static final int OP_RECORD_AUDIO = 27;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_PLAY_AUDIO = 28;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_READ_CLIPBOARD = 29;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_WRITE_CLIPBOARD = 30;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_TAKE_MEDIA_BUTTONS = 31;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_TAKE_AUDIO_FOCUS = 32;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_AUDIO_MASTER_VOLUME = 33;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_AUDIO_VOICE_VOLUME = 34;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_AUDIO_RING_VOLUME = 35;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_AUDIO_MEDIA_VOLUME = 36;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_AUDIO_ALARM_VOLUME = 37;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_AUDIO_NOTIFICATION_VOLUME = 38;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_AUDIO_BLUETOOTH_VOLUME = 39;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_WAKE_LOCK = 40;
+    /** @hide Continually monitoring location data. */
+    @UnsupportedAppUsage
+    public static final int OP_MONITOR_LOCATION = 41;
+    /** @hide Continually monitoring location data with a relatively high power request. */
+    @UnsupportedAppUsage
+    public static final int OP_MONITOR_HIGH_POWER_LOCATION = 42;
+    /** @hide Retrieve current usage stats via {@link UsageStatsManager}. */
+    @UnsupportedAppUsage
+    public static final int OP_GET_USAGE_STATS = 43;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_MUTE_MICROPHONE = 44;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_TOAST_WINDOW = 45;
+    /** @hide Capture the device's display contents and/or audio */
+    @UnsupportedAppUsage
+    public static final int OP_PROJECT_MEDIA = 46;
+    /** @hide Activate a VPN connection without user intervention. */
+    @UnsupportedAppUsage
+    public static final int OP_ACTIVATE_VPN = 47;
+    /** @hide Access the WallpaperManagerAPI to write wallpapers. */
+    @UnsupportedAppUsage
+    public static final int OP_WRITE_WALLPAPER = 48;
+    /** @hide Received the assist structure from an app. */
+    @UnsupportedAppUsage
+    public static final int OP_ASSIST_STRUCTURE = 49;
+    /** @hide Received a screenshot from assist. */
+    @UnsupportedAppUsage
+    public static final int OP_ASSIST_SCREENSHOT = 50;
+    /** @hide Read the phone state. */
+    @UnsupportedAppUsage
+    public static final int OP_READ_PHONE_STATE = 51;
+    /** @hide Add voicemail messages to the voicemail content provider. */
+    @UnsupportedAppUsage
+    public static final int OP_ADD_VOICEMAIL = 52;
+    /** @hide Access APIs for SIP calling over VOIP or WiFi. */
+    @UnsupportedAppUsage
+    public static final int OP_USE_SIP = 53;
+    /** @hide Intercept outgoing calls. */
+    @UnsupportedAppUsage
+    public static final int OP_PROCESS_OUTGOING_CALLS = 54;
+    /** @hide User the fingerprint API. */
+    @UnsupportedAppUsage
+    public static final int OP_USE_FINGERPRINT = 55;
+    /** @hide Access to body sensors such as heart rate, etc. */
+    @UnsupportedAppUsage
+    public static final int OP_BODY_SENSORS = 56;
+    /** @hide Read previously received cell broadcast messages. */
+    @UnsupportedAppUsage
+    public static final int OP_READ_CELL_BROADCASTS = 57;
+    /** @hide Inject mock location into the system. */
+    @UnsupportedAppUsage
+    public static final int OP_MOCK_LOCATION = 58;
+    /** @hide Read external storage. */
+    @UnsupportedAppUsage
+    public static final int OP_READ_EXTERNAL_STORAGE = 59;
+    /** @hide Write external storage. */
+    @UnsupportedAppUsage
+    public static final int OP_WRITE_EXTERNAL_STORAGE = 60;
+    /** @hide Turned on the screen. */
+    @UnsupportedAppUsage
+    public static final int OP_TURN_SCREEN_ON = 61;
+    /** @hide Get device accounts. */
+    @UnsupportedAppUsage
+    public static final int OP_GET_ACCOUNTS = 62;
+    /** @hide Control whether an application is allowed to run in the background. */
+    @UnsupportedAppUsage
+    public static final int OP_RUN_IN_BACKGROUND = 63;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_AUDIO_ACCESSIBILITY_VOLUME = 64;
+    /** @hide Read the phone number. */
+    @UnsupportedAppUsage
+    public static final int OP_READ_PHONE_NUMBERS = 65;
+    /** @hide Request package installs through package installer */
+    @UnsupportedAppUsage
+    public static final int OP_REQUEST_INSTALL_PACKAGES = 66;
+    /** @hide Enter picture-in-picture. */
+    @UnsupportedAppUsage
+    public static final int OP_PICTURE_IN_PICTURE = 67;
+    /** @hide Instant app start foreground service. */
+    @UnsupportedAppUsage
+    public static final int OP_INSTANT_APP_START_FOREGROUND = 68;
+    /** @hide Answer incoming phone calls */
+    @UnsupportedAppUsage
+    public static final int OP_ANSWER_PHONE_CALLS = 69;
+    /** @hide Run jobs when in background */
+    @UnsupportedAppUsage
+    public static final int OP_RUN_ANY_IN_BACKGROUND = 70;
+    /** @hide Change Wi-Fi connectivity state */
+    @UnsupportedAppUsage
+    public static final int OP_CHANGE_WIFI_STATE = 71;
+    /** @hide Request package deletion through package installer */
+    @UnsupportedAppUsage
+    public static final int OP_REQUEST_DELETE_PACKAGES = 72;
+    /** @hide Bind an accessibility service. */
+    @UnsupportedAppUsage
+    public static final int OP_BIND_ACCESSIBILITY_SERVICE = 73;
+    /** @hide Continue handover of a call from another app */
+    @UnsupportedAppUsage
+    public static final int OP_ACCEPT_HANDOVER = 74;
+    /** @hide Create and Manage IPsec Tunnels */
+    @UnsupportedAppUsage
+    public static final int OP_MANAGE_IPSEC_TUNNELS = 75;
+    /** @hide Any app start foreground service. */
+    @TestApi
+    public static final int OP_START_FOREGROUND = 76;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int OP_BLUETOOTH_SCAN = 77;
+    /** @hide Use the BiometricPrompt/BiometricManager APIs. */
+    public static final int OP_USE_BIOMETRIC = 78;
+    /** @hide Physical activity recognition. */
+    public static final int OP_ACTIVITY_RECOGNITION = 79;
+    /** @hide Financial app sms read. */
+    public static final int OP_SMS_FINANCIAL_TRANSACTIONS = 80;
+    /** @hide Read media of audio type. */
+    public static final int OP_READ_MEDIA_AUDIO = 81;
+    /** @hide Write media of audio type. */
+    public static final int OP_WRITE_MEDIA_AUDIO = 82;
+    /** @hide Read media of video type. */
+    public static final int OP_READ_MEDIA_VIDEO = 83;
+    /** @hide Write media of video type. */
+    public static final int OP_WRITE_MEDIA_VIDEO = 84;
+    /** @hide Read media of image type. */
+    public static final int OP_READ_MEDIA_IMAGES = 85;
+    /** @hide Write media of image type. */
+    public static final int OP_WRITE_MEDIA_IMAGES = 86;
+    /** @hide Has a legacy (non-isolated) view of storage. */
+    public static final int OP_LEGACY_STORAGE = 87;
+    /** @hide Accessing accessibility features */
+    public static final int OP_ACCESS_ACCESSIBILITY = 88;
+    /** @hide Read the device identifiers (IMEI / MEID, IMSI, SIM / Build serial) */
+    public static final int OP_READ_DEVICE_IDENTIFIERS = 89;
+    /** @hide */
+    @UnsupportedAppUsage
+    public static final int _NUM_OP = 90;
+
+    /** Access to coarse location information. */
+    public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
+    /** Access to fine location information. */
+    public static final String OPSTR_FINE_LOCATION =
+            "android:fine_location";
+    /** Continually monitoring location data. */
+    public static final String OPSTR_MONITOR_LOCATION
+            = "android:monitor_location";
+    /** Continually monitoring location data with a relatively high power request. */
+    public static final String OPSTR_MONITOR_HIGH_POWER_LOCATION
+            = "android:monitor_location_high_power";
+    /** Access to {@link android.app.usage.UsageStatsManager}. */
+    public static final String OPSTR_GET_USAGE_STATS
+            = "android:get_usage_stats";
+    /** Activate a VPN connection without user intervention. @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_ACTIVATE_VPN
+            = "android:activate_vpn";
+    /** Allows an application to read the user's contacts data. */
+    public static final String OPSTR_READ_CONTACTS
+            = "android:read_contacts";
+    /** Allows an application to write to the user's contacts data. */
+    public static final String OPSTR_WRITE_CONTACTS
+            = "android:write_contacts";
+    /** Allows an application to read the user's call log. */
+    public static final String OPSTR_READ_CALL_LOG
+            = "android:read_call_log";
+    /** Allows an application to write to the user's call log. */
+    public static final String OPSTR_WRITE_CALL_LOG
+            = "android:write_call_log";
+    /** Allows an application to read the user's calendar data. */
+    public static final String OPSTR_READ_CALENDAR
+            = "android:read_calendar";
+    /** Allows an application to write to the user's calendar data. */
+    public static final String OPSTR_WRITE_CALENDAR
+            = "android:write_calendar";
+    /** Allows an application to initiate a phone call. */
+    public static final String OPSTR_CALL_PHONE
+            = "android:call_phone";
+    /** Allows an application to read SMS messages. */
+    public static final String OPSTR_READ_SMS
+            = "android:read_sms";
+    /** Allows an application to receive SMS messages. */
+    public static final String OPSTR_RECEIVE_SMS
+            = "android:receive_sms";
+    /** Allows an application to receive MMS messages. */
+    public static final String OPSTR_RECEIVE_MMS
+            = "android:receive_mms";
+    /** Allows an application to receive WAP push messages. */
+    public static final String OPSTR_RECEIVE_WAP_PUSH
+            = "android:receive_wap_push";
+    /** Allows an application to send SMS messages. */
+    public static final String OPSTR_SEND_SMS
+            = "android:send_sms";
+    /** Required to be able to access the camera device. */
+    public static final String OPSTR_CAMERA
+            = "android:camera";
+    /** Required to be able to access the microphone device. */
+    public static final String OPSTR_RECORD_AUDIO
+            = "android:record_audio";
+    /** Required to access phone state related information. */
+    public static final String OPSTR_READ_PHONE_STATE
+            = "android:read_phone_state";
+    /** Required to access phone state related information. */
+    public static final String OPSTR_ADD_VOICEMAIL
+            = "android:add_voicemail";
+    /** Access APIs for SIP calling over VOIP or WiFi */
+    public static final String OPSTR_USE_SIP
+            = "android:use_sip";
+    /** Access APIs for diverting outgoing calls */
+    public static final String OPSTR_PROCESS_OUTGOING_CALLS
+            = "android:process_outgoing_calls";
+    /** Use the fingerprint API. */
+    public static final String OPSTR_USE_FINGERPRINT
+            = "android:use_fingerprint";
+    /** Access to body sensors such as heart rate, etc. */
+    public static final String OPSTR_BODY_SENSORS
+            = "android:body_sensors";
+    /** Read previously received cell broadcast messages. */
+    public static final String OPSTR_READ_CELL_BROADCASTS
+            = "android:read_cell_broadcasts";
+    /** Inject mock location into the system. */
+    public static final String OPSTR_MOCK_LOCATION
+            = "android:mock_location";
+    /** Read external storage. */
+    public static final String OPSTR_READ_EXTERNAL_STORAGE
+            = "android:read_external_storage";
+    /** Write external storage. */
+    public static final String OPSTR_WRITE_EXTERNAL_STORAGE
+            = "android:write_external_storage";
+    /** Required to draw on top of other apps. */
+    public static final String OPSTR_SYSTEM_ALERT_WINDOW
+            = "android:system_alert_window";
+    /** Required to write/modify/update system settingss. */
+    public static final String OPSTR_WRITE_SETTINGS
+            = "android:write_settings";
+    /** @hide Get device accounts. */
+    @SystemApi @TestApi
+    public static final String OPSTR_GET_ACCOUNTS
+            = "android:get_accounts";
+    public static final String OPSTR_READ_PHONE_NUMBERS
+            = "android:read_phone_numbers";
+    /** Access to picture-in-picture. */
+    public static final String OPSTR_PICTURE_IN_PICTURE
+            = "android:picture_in_picture";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_INSTANT_APP_START_FOREGROUND
+            = "android:instant_app_start_foreground";
+    /** Answer incoming phone calls */
+    public static final String OPSTR_ANSWER_PHONE_CALLS
+            = "android:answer_phone_calls";
+    /**
+     * Accept call handover
+     * @hide
+     */
+    @SystemApi @TestApi
+    public static final String OPSTR_ACCEPT_HANDOVER
+            = "android:accept_handover";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_GPS = "android:gps";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_VIBRATE = "android:vibrate";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WIFI_SCAN = "android:wifi_scan";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_POST_NOTIFICATION = "android:post_notification";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_NEIGHBORING_CELLS = "android:neighboring_cells";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WRITE_SMS = "android:write_sms";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST =
+            "android:receive_emergency_broadcast";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_READ_ICC_SMS = "android:read_icc_sms";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WRITE_ICC_SMS = "android:write_icc_sms";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_ACCESS_NOTIFICATIONS = "android:access_notifications";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_PLAY_AUDIO = "android:play_audio";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_READ_CLIPBOARD = "android:read_clipboard";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WRITE_CLIPBOARD = "android:write_clipboard";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_TAKE_MEDIA_BUTTONS = "android:take_media_buttons";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_TAKE_AUDIO_FOCUS = "android:take_audio_focus";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_MASTER_VOLUME = "android:audio_master_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_VOICE_VOLUME = "android:audio_voice_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_RING_VOLUME = "android:audio_ring_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_MEDIA_VOLUME = "android:audio_media_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_ALARM_VOLUME = "android:audio_alarm_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_NOTIFICATION_VOLUME =
+            "android:audio_notification_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_BLUETOOTH_VOLUME = "android:audio_bluetooth_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WAKE_LOCK = "android:wake_lock";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_MUTE_MICROPHONE = "android:mute_microphone";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_TOAST_WINDOW = "android:toast_window";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_PROJECT_MEDIA = "android:project_media";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_WRITE_WALLPAPER = "android:write_wallpaper";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_ASSIST_STRUCTURE = "android:assist_structure";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_ASSIST_SCREENSHOT = "android:assist_screenshot";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_TURN_SCREEN_ON = "android:turn_screen_on";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_RUN_IN_BACKGROUND = "android:run_in_background";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_AUDIO_ACCESSIBILITY_VOLUME =
+            "android:audio_accessibility_volume";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_REQUEST_INSTALL_PACKAGES = "android:request_install_packages";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_RUN_ANY_IN_BACKGROUND = "android:run_any_in_background";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_CHANGE_WIFI_STATE = "android:change_wifi_state";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_REQUEST_DELETE_PACKAGES = "android:request_delete_packages";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE =
+            "android:bind_accessibility_service";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_MANAGE_IPSEC_TUNNELS = "android:manage_ipsec_tunnels";
+    /** @hide */
+    @SystemApi @TestApi
+    public static final String OPSTR_START_FOREGROUND = "android:start_foreground";
+    /** @hide */
+    public static final String OPSTR_BLUETOOTH_SCAN = "android:bluetooth_scan";
+
+    /** @hide Use the BiometricPrompt/BiometricManager APIs. */
+    public static final String OPSTR_USE_BIOMETRIC = "android:use_biometric";
+
+    /** @hide Recognize physical activity. */
+    public static final String OPSTR_ACTIVITY_RECOGNITION = "android:activity_recognition";
+
+    /** @hide Financial app read sms. */
+    public static final String OPSTR_SMS_FINANCIAL_TRANSACTIONS =
+            "android:sms_financial_transactions";
+
+    /** @hide Read media of audio type. */
+    public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio";
+    /** @hide Write media of audio type. */
+    public static final String OPSTR_WRITE_MEDIA_AUDIO = "android:write_media_audio";
+    /** @hide Read media of video type. */
+    public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
+    /** @hide Write media of video type. */
+    public static final String OPSTR_WRITE_MEDIA_VIDEO = "android:write_media_video";
+    /** @hide Read media of image type. */
+    public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
+    /** @hide Write media of image type. */
+    public static final String OPSTR_WRITE_MEDIA_IMAGES = "android:write_media_images";
+    /** @hide Has a legacy (non-isolated) view of storage. */
+    @TestApi
+    @SystemApi
+    public static final String OPSTR_LEGACY_STORAGE = "android:legacy_storage";
+    /** @hide Interact with accessibility. */
+    @SystemApi
+    public static final String OPSTR_ACCESS_ACCESSIBILITY = "android:access_accessibility";
+    /** @hide Read device identifiers */
+    public static final String OPSTR_READ_DEVICE_IDENTIFIERS = "android:read_device_identifiers";
+
+    // Warning: If an permission is added here it also has to be added to
+    // com.android.packageinstaller.permission.utils.EventLogger
+    private static final int[] RUNTIME_AND_APPOP_PERMISSIONS_OPS = {
+            // RUNTIME PERMISSIONS
+            // Contacts
+            OP_READ_CONTACTS,
+            OP_WRITE_CONTACTS,
+            OP_GET_ACCOUNTS,
+            // Calendar
+            OP_READ_CALENDAR,
+            OP_WRITE_CALENDAR,
+            // SMS
+            OP_SEND_SMS,
+            OP_RECEIVE_SMS,
+            OP_READ_SMS,
+            OP_RECEIVE_WAP_PUSH,
+            OP_RECEIVE_MMS,
+            OP_READ_CELL_BROADCASTS,
+            // Storage
+            OP_READ_EXTERNAL_STORAGE,
+            OP_WRITE_EXTERNAL_STORAGE,
+            // Location
+            OP_COARSE_LOCATION,
+            OP_FINE_LOCATION,
+            // Phone
+            OP_READ_PHONE_STATE,
+            OP_READ_PHONE_NUMBERS,
+            OP_CALL_PHONE,
+            OP_READ_CALL_LOG,
+            OP_WRITE_CALL_LOG,
+            OP_ADD_VOICEMAIL,
+            OP_USE_SIP,
+            OP_PROCESS_OUTGOING_CALLS,
+            OP_ANSWER_PHONE_CALLS,
+            OP_ACCEPT_HANDOVER,
+            // Microphone
+            OP_RECORD_AUDIO,
+            // Camera
+            OP_CAMERA,
+            // Body sensors
+            OP_BODY_SENSORS,
+            // Activity recognition
+            OP_ACTIVITY_RECOGNITION,
+            // Aural
+            OP_READ_MEDIA_AUDIO,
+            OP_WRITE_MEDIA_AUDIO,
+            // Visual
+            OP_READ_MEDIA_VIDEO,
+            OP_WRITE_MEDIA_VIDEO,
+            OP_READ_MEDIA_IMAGES,
+            OP_WRITE_MEDIA_IMAGES,
+
+            // APPOP PERMISSIONS
+            OP_ACCESS_NOTIFICATIONS,
+            OP_SYSTEM_ALERT_WINDOW,
+            OP_WRITE_SETTINGS,
+            OP_REQUEST_INSTALL_PACKAGES,
+            OP_START_FOREGROUND,
+            OP_SMS_FINANCIAL_TRANSACTIONS,
+    };
+
+    /**
+     * This maps each operation to the operation that serves as the
+     * switch to determine whether it is allowed.  Generally this is
+     * a 1:1 mapping, but for some things (like location) that have
+     * multiple low-level operations being tracked that should be
+     * presented to the user as one switch then this can be used to
+     * make them all controlled by the same single operation.
+     */
+    private static int[] sOpToSwitch = new int[] {
+            OP_COARSE_LOCATION,                 // COARSE_LOCATION
+            OP_COARSE_LOCATION,                 // FINE_LOCATION
+            OP_COARSE_LOCATION,                 // GPS
+            OP_VIBRATE,                         // VIBRATE
+            OP_READ_CONTACTS,                   // READ_CONTACTS
+            OP_WRITE_CONTACTS,                  // WRITE_CONTACTS
+            OP_READ_CALL_LOG,                   // READ_CALL_LOG
+            OP_WRITE_CALL_LOG,                  // WRITE_CALL_LOG
+            OP_READ_CALENDAR,                   // READ_CALENDAR
+            OP_WRITE_CALENDAR,                  // WRITE_CALENDAR
+            OP_COARSE_LOCATION,                 // WIFI_SCAN
+            OP_POST_NOTIFICATION,               // POST_NOTIFICATION
+            OP_COARSE_LOCATION,                 // NEIGHBORING_CELLS
+            OP_CALL_PHONE,                      // CALL_PHONE
+            OP_READ_SMS,                        // READ_SMS
+            OP_WRITE_SMS,                       // WRITE_SMS
+            OP_RECEIVE_SMS,                     // RECEIVE_SMS
+            OP_RECEIVE_SMS,                     // RECEIVE_EMERGECY_SMS
+            OP_RECEIVE_MMS,                     // RECEIVE_MMS
+            OP_RECEIVE_WAP_PUSH,                // RECEIVE_WAP_PUSH
+            OP_SEND_SMS,                        // SEND_SMS
+            OP_READ_SMS,                        // READ_ICC_SMS
+            OP_WRITE_SMS,                       // WRITE_ICC_SMS
+            OP_WRITE_SETTINGS,                  // WRITE_SETTINGS
+            OP_SYSTEM_ALERT_WINDOW,             // SYSTEM_ALERT_WINDOW
+            OP_ACCESS_NOTIFICATIONS,            // ACCESS_NOTIFICATIONS
+            OP_CAMERA,                          // CAMERA
+            OP_RECORD_AUDIO,                    // RECORD_AUDIO
+            OP_PLAY_AUDIO,                      // PLAY_AUDIO
+            OP_READ_CLIPBOARD,                  // READ_CLIPBOARD
+            OP_WRITE_CLIPBOARD,                 // WRITE_CLIPBOARD
+            OP_TAKE_MEDIA_BUTTONS,              // TAKE_MEDIA_BUTTONS
+            OP_TAKE_AUDIO_FOCUS,                // TAKE_AUDIO_FOCUS
+            OP_AUDIO_MASTER_VOLUME,             // AUDIO_MASTER_VOLUME
+            OP_AUDIO_VOICE_VOLUME,              // AUDIO_VOICE_VOLUME
+            OP_AUDIO_RING_VOLUME,               // AUDIO_RING_VOLUME
+            OP_AUDIO_MEDIA_VOLUME,              // AUDIO_MEDIA_VOLUME
+            OP_AUDIO_ALARM_VOLUME,              // AUDIO_ALARM_VOLUME
+            OP_AUDIO_NOTIFICATION_VOLUME,       // AUDIO_NOTIFICATION_VOLUME
+            OP_AUDIO_BLUETOOTH_VOLUME,          // AUDIO_BLUETOOTH_VOLUME
+            OP_WAKE_LOCK,                       // WAKE_LOCK
+            OP_COARSE_LOCATION,                 // MONITOR_LOCATION
+            OP_COARSE_LOCATION,                 // MONITOR_HIGH_POWER_LOCATION
+            OP_GET_USAGE_STATS,                 // GET_USAGE_STATS
+            OP_MUTE_MICROPHONE,                 // MUTE_MICROPHONE
+            OP_TOAST_WINDOW,                    // TOAST_WINDOW
+            OP_PROJECT_MEDIA,                   // PROJECT_MEDIA
+            OP_ACTIVATE_VPN,                    // ACTIVATE_VPN
+            OP_WRITE_WALLPAPER,                 // WRITE_WALLPAPER
+            OP_ASSIST_STRUCTURE,                // ASSIST_STRUCTURE
+            OP_ASSIST_SCREENSHOT,               // ASSIST_SCREENSHOT
+            OP_READ_PHONE_STATE,                // READ_PHONE_STATE
+            OP_ADD_VOICEMAIL,                   // ADD_VOICEMAIL
+            OP_USE_SIP,                         // USE_SIP
+            OP_PROCESS_OUTGOING_CALLS,          // PROCESS_OUTGOING_CALLS
+            OP_USE_FINGERPRINT,                 // USE_FINGERPRINT
+            OP_BODY_SENSORS,                    // BODY_SENSORS
+            OP_READ_CELL_BROADCASTS,            // READ_CELL_BROADCASTS
+            OP_MOCK_LOCATION,                   // MOCK_LOCATION
+            OP_READ_EXTERNAL_STORAGE,           // READ_EXTERNAL_STORAGE
+            OP_WRITE_EXTERNAL_STORAGE,          // WRITE_EXTERNAL_STORAGE
+            OP_TURN_SCREEN_ON,                  // TURN_SCREEN_ON
+            OP_GET_ACCOUNTS,                    // GET_ACCOUNTS
+            OP_RUN_IN_BACKGROUND,               // RUN_IN_BACKGROUND
+            OP_AUDIO_ACCESSIBILITY_VOLUME,      // AUDIO_ACCESSIBILITY_VOLUME
+            OP_READ_PHONE_NUMBERS,              // READ_PHONE_NUMBERS
+            OP_REQUEST_INSTALL_PACKAGES,        // REQUEST_INSTALL_PACKAGES
+            OP_PICTURE_IN_PICTURE,              // ENTER_PICTURE_IN_PICTURE_ON_HIDE
+            OP_INSTANT_APP_START_FOREGROUND,    // INSTANT_APP_START_FOREGROUND
+            OP_ANSWER_PHONE_CALLS,              // ANSWER_PHONE_CALLS
+            OP_RUN_ANY_IN_BACKGROUND,           // OP_RUN_ANY_IN_BACKGROUND
+            OP_CHANGE_WIFI_STATE,               // OP_CHANGE_WIFI_STATE
+            OP_REQUEST_DELETE_PACKAGES,         // OP_REQUEST_DELETE_PACKAGES
+            OP_BIND_ACCESSIBILITY_SERVICE,      // OP_BIND_ACCESSIBILITY_SERVICE
+            OP_ACCEPT_HANDOVER,                 // ACCEPT_HANDOVER
+            OP_MANAGE_IPSEC_TUNNELS,            // MANAGE_IPSEC_HANDOVERS
+            OP_START_FOREGROUND,                // START_FOREGROUND
+            OP_COARSE_LOCATION,                 // BLUETOOTH_SCAN
+            OP_USE_BIOMETRIC,                   // BIOMETRIC
+            OP_ACTIVITY_RECOGNITION,            // ACTIVITY_RECOGNITION
+            OP_SMS_FINANCIAL_TRANSACTIONS,      // SMS_FINANCIAL_TRANSACTIONS
+            OP_READ_MEDIA_AUDIO,                // READ_MEDIA_AUDIO
+            OP_WRITE_MEDIA_AUDIO,               // WRITE_MEDIA_AUDIO
+            OP_READ_MEDIA_VIDEO,                // READ_MEDIA_VIDEO
+            OP_WRITE_MEDIA_VIDEO,               // WRITE_MEDIA_VIDEO
+            OP_READ_MEDIA_IMAGES,               // READ_MEDIA_IMAGES
+            OP_WRITE_MEDIA_IMAGES,              // WRITE_MEDIA_IMAGES
+            OP_LEGACY_STORAGE,                  // LEGACY_STORAGE
+            OP_ACCESS_ACCESSIBILITY,            // ACCESS_ACCESSIBILITY
+            OP_READ_DEVICE_IDENTIFIERS,         // READ_DEVICE_IDENTIFIERS
+    };
+
+    /**
+     * This maps each operation to the public string constant for it.
+     */
+    private static String[] sOpToString = new String[]{
+            OPSTR_COARSE_LOCATION,
+            OPSTR_FINE_LOCATION,
+            OPSTR_GPS,
+            OPSTR_VIBRATE,
+            OPSTR_READ_CONTACTS,
+            OPSTR_WRITE_CONTACTS,
+            OPSTR_READ_CALL_LOG,
+            OPSTR_WRITE_CALL_LOG,
+            OPSTR_READ_CALENDAR,
+            OPSTR_WRITE_CALENDAR,
+            OPSTR_WIFI_SCAN,
+            OPSTR_POST_NOTIFICATION,
+            OPSTR_NEIGHBORING_CELLS,
+            OPSTR_CALL_PHONE,
+            OPSTR_READ_SMS,
+            OPSTR_WRITE_SMS,
+            OPSTR_RECEIVE_SMS,
+            OPSTR_RECEIVE_EMERGENCY_BROADCAST,
+            OPSTR_RECEIVE_MMS,
+            OPSTR_RECEIVE_WAP_PUSH,
+            OPSTR_SEND_SMS,
+            OPSTR_READ_ICC_SMS,
+            OPSTR_WRITE_ICC_SMS,
+            OPSTR_WRITE_SETTINGS,
+            OPSTR_SYSTEM_ALERT_WINDOW,
+            OPSTR_ACCESS_NOTIFICATIONS,
+            OPSTR_CAMERA,
+            OPSTR_RECORD_AUDIO,
+            OPSTR_PLAY_AUDIO,
+            OPSTR_READ_CLIPBOARD,
+            OPSTR_WRITE_CLIPBOARD,
+            OPSTR_TAKE_MEDIA_BUTTONS,
+            OPSTR_TAKE_AUDIO_FOCUS,
+            OPSTR_AUDIO_MASTER_VOLUME,
+            OPSTR_AUDIO_VOICE_VOLUME,
+            OPSTR_AUDIO_RING_VOLUME,
+            OPSTR_AUDIO_MEDIA_VOLUME,
+            OPSTR_AUDIO_ALARM_VOLUME,
+            OPSTR_AUDIO_NOTIFICATION_VOLUME,
+            OPSTR_AUDIO_BLUETOOTH_VOLUME,
+            OPSTR_WAKE_LOCK,
+            OPSTR_MONITOR_LOCATION,
+            OPSTR_MONITOR_HIGH_POWER_LOCATION,
+            OPSTR_GET_USAGE_STATS,
+            OPSTR_MUTE_MICROPHONE,
+            OPSTR_TOAST_WINDOW,
+            OPSTR_PROJECT_MEDIA,
+            OPSTR_ACTIVATE_VPN,
+            OPSTR_WRITE_WALLPAPER,
+            OPSTR_ASSIST_STRUCTURE,
+            OPSTR_ASSIST_SCREENSHOT,
+            OPSTR_READ_PHONE_STATE,
+            OPSTR_ADD_VOICEMAIL,
+            OPSTR_USE_SIP,
+            OPSTR_PROCESS_OUTGOING_CALLS,
+            OPSTR_USE_FINGERPRINT,
+            OPSTR_BODY_SENSORS,
+            OPSTR_READ_CELL_BROADCASTS,
+            OPSTR_MOCK_LOCATION,
+            OPSTR_READ_EXTERNAL_STORAGE,
+            OPSTR_WRITE_EXTERNAL_STORAGE,
+            OPSTR_TURN_SCREEN_ON,
+            OPSTR_GET_ACCOUNTS,
+            OPSTR_RUN_IN_BACKGROUND,
+            OPSTR_AUDIO_ACCESSIBILITY_VOLUME,
+            OPSTR_READ_PHONE_NUMBERS,
+            OPSTR_REQUEST_INSTALL_PACKAGES,
+            OPSTR_PICTURE_IN_PICTURE,
+            OPSTR_INSTANT_APP_START_FOREGROUND,
+            OPSTR_ANSWER_PHONE_CALLS,
+            OPSTR_RUN_ANY_IN_BACKGROUND,
+            OPSTR_CHANGE_WIFI_STATE,
+            OPSTR_REQUEST_DELETE_PACKAGES,
+            OPSTR_BIND_ACCESSIBILITY_SERVICE,
+            OPSTR_ACCEPT_HANDOVER,
+            OPSTR_MANAGE_IPSEC_TUNNELS,
+            OPSTR_START_FOREGROUND,
+            OPSTR_BLUETOOTH_SCAN,
+            OPSTR_USE_BIOMETRIC,
+            OPSTR_ACTIVITY_RECOGNITION,
+            OPSTR_SMS_FINANCIAL_TRANSACTIONS,
+            OPSTR_READ_MEDIA_AUDIO,
+            OPSTR_WRITE_MEDIA_AUDIO,
+            OPSTR_READ_MEDIA_VIDEO,
+            OPSTR_WRITE_MEDIA_VIDEO,
+            OPSTR_READ_MEDIA_IMAGES,
+            OPSTR_WRITE_MEDIA_IMAGES,
+            OPSTR_LEGACY_STORAGE,
+            OPSTR_ACCESS_ACCESSIBILITY,
+            OPSTR_READ_DEVICE_IDENTIFIERS,
+    };
+
+    /**
+     * This provides a simple name for each operation to be used
+     * in debug output.
+     */
+    private static String[] sOpNames = new String[] {
+            "COARSE_LOCATION",
+            "FINE_LOCATION",
+            "GPS",
+            "VIBRATE",
+            "READ_CONTACTS",
+            "WRITE_CONTACTS",
+            "READ_CALL_LOG",
+            "WRITE_CALL_LOG",
+            "READ_CALENDAR",
+            "WRITE_CALENDAR",
+            "WIFI_SCAN",
+            "POST_NOTIFICATION",
+            "NEIGHBORING_CELLS",
+            "CALL_PHONE",
+            "READ_SMS",
+            "WRITE_SMS",
+            "RECEIVE_SMS",
+            "RECEIVE_EMERGECY_SMS",
+            "RECEIVE_MMS",
+            "RECEIVE_WAP_PUSH",
+            "SEND_SMS",
+            "READ_ICC_SMS",
+            "WRITE_ICC_SMS",
+            "WRITE_SETTINGS",
+            "SYSTEM_ALERT_WINDOW",
+            "ACCESS_NOTIFICATIONS",
+            "CAMERA",
+            "RECORD_AUDIO",
+            "PLAY_AUDIO",
+            "READ_CLIPBOARD",
+            "WRITE_CLIPBOARD",
+            "TAKE_MEDIA_BUTTONS",
+            "TAKE_AUDIO_FOCUS",
+            "AUDIO_MASTER_VOLUME",
+            "AUDIO_VOICE_VOLUME",
+            "AUDIO_RING_VOLUME",
+            "AUDIO_MEDIA_VOLUME",
+            "AUDIO_ALARM_VOLUME",
+            "AUDIO_NOTIFICATION_VOLUME",
+            "AUDIO_BLUETOOTH_VOLUME",
+            "WAKE_LOCK",
+            "MONITOR_LOCATION",
+            "MONITOR_HIGH_POWER_LOCATION",
+            "GET_USAGE_STATS",
+            "MUTE_MICROPHONE",
+            "TOAST_WINDOW",
+            "PROJECT_MEDIA",
+            "ACTIVATE_VPN",
+            "WRITE_WALLPAPER",
+            "ASSIST_STRUCTURE",
+            "ASSIST_SCREENSHOT",
+            "READ_PHONE_STATE",
+            "ADD_VOICEMAIL",
+            "USE_SIP",
+            "PROCESS_OUTGOING_CALLS",
+            "USE_FINGERPRINT",
+            "BODY_SENSORS",
+            "READ_CELL_BROADCASTS",
+            "MOCK_LOCATION",
+            "READ_EXTERNAL_STORAGE",
+            "WRITE_EXTERNAL_STORAGE",
+            "TURN_ON_SCREEN",
+            "GET_ACCOUNTS",
+            "RUN_IN_BACKGROUND",
+            "AUDIO_ACCESSIBILITY_VOLUME",
+            "READ_PHONE_NUMBERS",
+            "REQUEST_INSTALL_PACKAGES",
+            "PICTURE_IN_PICTURE",
+            "INSTANT_APP_START_FOREGROUND",
+            "ANSWER_PHONE_CALLS",
+            "RUN_ANY_IN_BACKGROUND",
+            "CHANGE_WIFI_STATE",
+            "REQUEST_DELETE_PACKAGES",
+            "BIND_ACCESSIBILITY_SERVICE",
+            "ACCEPT_HANDOVER",
+            "MANAGE_IPSEC_TUNNELS",
+            "START_FOREGROUND",
+            "BLUETOOTH_SCAN",
+            "USE_BIOMETRIC",
+            "ACTIVITY_RECOGNITION",
+            "SMS_FINANCIAL_TRANSACTIONS",
+            "READ_MEDIA_AUDIO",
+            "WRITE_MEDIA_AUDIO",
+            "READ_MEDIA_VIDEO",
+            "WRITE_MEDIA_VIDEO",
+            "READ_MEDIA_IMAGES",
+            "WRITE_MEDIA_IMAGES",
+            "LEGACY_STORAGE",
+            "ACCESS_ACCESSIBILITY",
+            "READ_DEVICE_IDENTIFIERS",
+    };
+
+    /**
+     * This optionally maps a permission to an operation.  If there
+     * is no permission associated with an operation, it is null.
+     */
+    @UnsupportedAppUsage
+    private static String[] sOpPerms = new String[] {
+            android.Manifest.permission.ACCESS_COARSE_LOCATION,
+            android.Manifest.permission.ACCESS_FINE_LOCATION,
+            null,
+            android.Manifest.permission.VIBRATE,
+            android.Manifest.permission.READ_CONTACTS,
+            android.Manifest.permission.WRITE_CONTACTS,
+            android.Manifest.permission.READ_CALL_LOG,
+            android.Manifest.permission.WRITE_CALL_LOG,
+            android.Manifest.permission.READ_CALENDAR,
+            android.Manifest.permission.WRITE_CALENDAR,
+            android.Manifest.permission.ACCESS_WIFI_STATE,
+            null, // no permission required for notifications
+            null, // neighboring cells shares the coarse location perm
+            android.Manifest.permission.CALL_PHONE,
+            android.Manifest.permission.READ_SMS,
+            null, // no permission required for writing sms
+            android.Manifest.permission.RECEIVE_SMS,
+            android.Manifest.permission.RECEIVE_EMERGENCY_BROADCAST,
+            android.Manifest.permission.RECEIVE_MMS,
+            android.Manifest.permission.RECEIVE_WAP_PUSH,
+            android.Manifest.permission.SEND_SMS,
+            android.Manifest.permission.READ_SMS,
+            null, // no permission required for writing icc sms
+            android.Manifest.permission.WRITE_SETTINGS,
+            android.Manifest.permission.SYSTEM_ALERT_WINDOW,
+            android.Manifest.permission.ACCESS_NOTIFICATIONS,
+            android.Manifest.permission.CAMERA,
+            android.Manifest.permission.RECORD_AUDIO,
+            null, // no permission for playing audio
+            null, // no permission for reading clipboard
+            null, // no permission for writing clipboard
+            null, // no permission for taking media buttons
+            null, // no permission for taking audio focus
+            null, // no permission for changing master volume
+            null, // no permission for changing voice volume
+            null, // no permission for changing ring volume
+            null, // no permission for changing media volume
+            null, // no permission for changing alarm volume
+            null, // no permission for changing notification volume
+            null, // no permission for changing bluetooth volume
+            android.Manifest.permission.WAKE_LOCK,
+            null, // no permission for generic location monitoring
+            null, // no permission for high power location monitoring
+            android.Manifest.permission.PACKAGE_USAGE_STATS,
+            null, // no permission for muting/unmuting microphone
+            null, // no permission for displaying toasts
+            null, // no permission for projecting media
+            null, // no permission for activating vpn
+            null, // no permission for supporting wallpaper
+            null, // no permission for receiving assist structure
+            null, // no permission for receiving assist screenshot
+            Manifest.permission.READ_PHONE_STATE,
+            Manifest.permission.ADD_VOICEMAIL,
+            Manifest.permission.USE_SIP,
+            Manifest.permission.PROCESS_OUTGOING_CALLS,
+            Manifest.permission.USE_FINGERPRINT,
+            Manifest.permission.BODY_SENSORS,
+            Manifest.permission.READ_CELL_BROADCASTS,
+            null,
+            Manifest.permission.READ_EXTERNAL_STORAGE,
+            Manifest.permission.WRITE_EXTERNAL_STORAGE,
+            null, // no permission for turning the screen on
+            Manifest.permission.GET_ACCOUNTS,
+            null, // no permission for running in background
+            null, // no permission for changing accessibility volume
+            Manifest.permission.READ_PHONE_NUMBERS,
+            Manifest.permission.REQUEST_INSTALL_PACKAGES,
+            null, // no permission for entering picture-in-picture on hide
+            Manifest.permission.INSTANT_APP_FOREGROUND_SERVICE,
+            Manifest.permission.ANSWER_PHONE_CALLS,
+            null, // no permission for OP_RUN_ANY_IN_BACKGROUND
+            Manifest.permission.CHANGE_WIFI_STATE,
+            Manifest.permission.REQUEST_DELETE_PACKAGES,
+            Manifest.permission.BIND_ACCESSIBILITY_SERVICE,
+            Manifest.permission.ACCEPT_HANDOVER,
+            null, // no permission for OP_MANAGE_IPSEC_TUNNELS
+            Manifest.permission.FOREGROUND_SERVICE,
+            null, // no permission for OP_BLUETOOTH_SCAN
+            Manifest.permission.USE_BIOMETRIC,
+            Manifest.permission.ACTIVITY_RECOGNITION,
+            Manifest.permission.SMS_FINANCIAL_TRANSACTIONS,
+            null,
+            null, // no permission for OP_WRITE_MEDIA_AUDIO
+            null,
+            null, // no permission for OP_WRITE_MEDIA_VIDEO
+            null,
+            null, // no permission for OP_WRITE_MEDIA_IMAGES
+            null, // no permission for OP_LEGACY_STORAGE
+            null, // no permission for OP_ACCESS_ACCESSIBILITY
+            null, // no direct permission for OP_READ_DEVICE_IDENTIFIERS
+    };
+
+    /**
+     * Specifies whether an Op should be restricted by a user restriction.
+     * Each Op should be filled with a restriction string from UserManager or
+     * null to specify it is not affected by any user restriction.
+     */
+    private static String[] sOpRestrictions = new String[] {
+            UserManager.DISALLOW_SHARE_LOCATION, //COARSE_LOCATION
+            UserManager.DISALLOW_SHARE_LOCATION, //FINE_LOCATION
+            UserManager.DISALLOW_SHARE_LOCATION, //GPS
+            null, //VIBRATE
+            null, //READ_CONTACTS
+            null, //WRITE_CONTACTS
+            UserManager.DISALLOW_OUTGOING_CALLS, //READ_CALL_LOG
+            UserManager.DISALLOW_OUTGOING_CALLS, //WRITE_CALL_LOG
+            null, //READ_CALENDAR
+            null, //WRITE_CALENDAR
+            UserManager.DISALLOW_SHARE_LOCATION, //WIFI_SCAN
+            null, //POST_NOTIFICATION
+            null, //NEIGHBORING_CELLS
+            null, //CALL_PHONE
+            UserManager.DISALLOW_SMS, //READ_SMS
+            UserManager.DISALLOW_SMS, //WRITE_SMS
+            UserManager.DISALLOW_SMS, //RECEIVE_SMS
+            null, //RECEIVE_EMERGENCY_SMS
+            UserManager.DISALLOW_SMS, //RECEIVE_MMS
+            null, //RECEIVE_WAP_PUSH
+            UserManager.DISALLOW_SMS, //SEND_SMS
+            UserManager.DISALLOW_SMS, //READ_ICC_SMS
+            UserManager.DISALLOW_SMS, //WRITE_ICC_SMS
+            null, //WRITE_SETTINGS
+            UserManager.DISALLOW_CREATE_WINDOWS, //SYSTEM_ALERT_WINDOW
+            null, //ACCESS_NOTIFICATIONS
+            UserManager.DISALLOW_CAMERA, //CAMERA
+            UserManager.DISALLOW_RECORD_AUDIO, //RECORD_AUDIO
+            null, //PLAY_AUDIO
+            null, //READ_CLIPBOARD
+            null, //WRITE_CLIPBOARD
+            null, //TAKE_MEDIA_BUTTONS
+            null, //TAKE_AUDIO_FOCUS
+            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_MASTER_VOLUME
+            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_VOICE_VOLUME
+            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_RING_VOLUME
+            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_MEDIA_VOLUME
+            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ALARM_VOLUME
+            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_NOTIFICATION_VOLUME
+            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_BLUETOOTH_VOLUME
+            null, //WAKE_LOCK
+            UserManager.DISALLOW_SHARE_LOCATION, //MONITOR_LOCATION
+            UserManager.DISALLOW_SHARE_LOCATION, //MONITOR_HIGH_POWER_LOCATION
+            null, //GET_USAGE_STATS
+            UserManager.DISALLOW_UNMUTE_MICROPHONE, // MUTE_MICROPHONE
+            UserManager.DISALLOW_CREATE_WINDOWS, // TOAST_WINDOW
+            null, //PROJECT_MEDIA
+            null, // ACTIVATE_VPN
+            UserManager.DISALLOW_WALLPAPER, // WRITE_WALLPAPER
+            null, // ASSIST_STRUCTURE
+            null, // ASSIST_SCREENSHOT
+            null, // READ_PHONE_STATE
+            null, // ADD_VOICEMAIL
+            null, // USE_SIP
+            null, // PROCESS_OUTGOING_CALLS
+            null, // USE_FINGERPRINT
+            null, // BODY_SENSORS
+            null, // READ_CELL_BROADCASTS
+            null, // MOCK_LOCATION
+            null, // READ_EXTERNAL_STORAGE
+            null, // WRITE_EXTERNAL_STORAGE
+            null, // TURN_ON_SCREEN
+            null, // GET_ACCOUNTS
+            null, // RUN_IN_BACKGROUND
+            UserManager.DISALLOW_ADJUST_VOLUME, //AUDIO_ACCESSIBILITY_VOLUME
+            null, // READ_PHONE_NUMBERS
+            null, // REQUEST_INSTALL_PACKAGES
+            null, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
+            null, // INSTANT_APP_START_FOREGROUND
+            null, // ANSWER_PHONE_CALLS
+            null, // OP_RUN_ANY_IN_BACKGROUND
+            null, // OP_CHANGE_WIFI_STATE
+            null, // REQUEST_DELETE_PACKAGES
+            null, // OP_BIND_ACCESSIBILITY_SERVICE
+            null, // ACCEPT_HANDOVER
+            null, // MANAGE_IPSEC_TUNNELS
+            null, // START_FOREGROUND
+            null, // maybe should be UserManager.DISALLOW_SHARE_LOCATION, //BLUETOOTH_SCAN
+            null, // USE_BIOMETRIC
+            null, // ACTIVITY_RECOGNITION
+            UserManager.DISALLOW_SMS, // SMS_FINANCIAL_TRANSACTIONS
+            null, // READ_MEDIA_AUDIO
+            null, // WRITE_MEDIA_AUDIO
+            null, // READ_MEDIA_VIDEO
+            null, // WRITE_MEDIA_VIDEO
+            null, // READ_MEDIA_IMAGES
+            null, // WRITE_MEDIA_IMAGES
+            null, // LEGACY_STORAGE
+            null, // ACCESS_ACCESSIBILITY
+            null, // READ_DEVICE_IDENTIFIERS
+    };
+
+    /**
+     * This specifies whether each option should allow the system
+     * (and system ui) to bypass the user restriction when active.
+     */
+    private static boolean[] sOpAllowSystemRestrictionBypass = new boolean[] {
+            true, //COARSE_LOCATION
+            true, //FINE_LOCATION
+            false, //GPS
+            false, //VIBRATE
+            false, //READ_CONTACTS
+            false, //WRITE_CONTACTS
+            false, //READ_CALL_LOG
+            false, //WRITE_CALL_LOG
+            false, //READ_CALENDAR
+            false, //WRITE_CALENDAR
+            true, //WIFI_SCAN
+            false, //POST_NOTIFICATION
+            false, //NEIGHBORING_CELLS
+            false, //CALL_PHONE
+            false, //READ_SMS
+            false, //WRITE_SMS
+            false, //RECEIVE_SMS
+            false, //RECEIVE_EMERGECY_SMS
+            false, //RECEIVE_MMS
+            false, //RECEIVE_WAP_PUSH
+            false, //SEND_SMS
+            false, //READ_ICC_SMS
+            false, //WRITE_ICC_SMS
+            false, //WRITE_SETTINGS
+            true, //SYSTEM_ALERT_WINDOW
+            false, //ACCESS_NOTIFICATIONS
+            false, //CAMERA
+            false, //RECORD_AUDIO
+            false, //PLAY_AUDIO
+            false, //READ_CLIPBOARD
+            false, //WRITE_CLIPBOARD
+            false, //TAKE_MEDIA_BUTTONS
+            false, //TAKE_AUDIO_FOCUS
+            false, //AUDIO_MASTER_VOLUME
+            false, //AUDIO_VOICE_VOLUME
+            false, //AUDIO_RING_VOLUME
+            false, //AUDIO_MEDIA_VOLUME
+            false, //AUDIO_ALARM_VOLUME
+            false, //AUDIO_NOTIFICATION_VOLUME
+            false, //AUDIO_BLUETOOTH_VOLUME
+            false, //WAKE_LOCK
+            false, //MONITOR_LOCATION
+            false, //MONITOR_HIGH_POWER_LOCATION
+            false, //GET_USAGE_STATS
+            false, //MUTE_MICROPHONE
+            true, //TOAST_WINDOW
+            false, //PROJECT_MEDIA
+            false, //ACTIVATE_VPN
+            false, //WALLPAPER
+            false, //ASSIST_STRUCTURE
+            false, //ASSIST_SCREENSHOT
+            false, //READ_PHONE_STATE
+            false, //ADD_VOICEMAIL
+            false, // USE_SIP
+            false, // PROCESS_OUTGOING_CALLS
+            false, // USE_FINGERPRINT
+            false, // BODY_SENSORS
+            false, // READ_CELL_BROADCASTS
+            false, // MOCK_LOCATION
+            false, // READ_EXTERNAL_STORAGE
+            false, // WRITE_EXTERNAL_STORAGE
+            false, // TURN_ON_SCREEN
+            false, // GET_ACCOUNTS
+            false, // RUN_IN_BACKGROUND
+            false, // AUDIO_ACCESSIBILITY_VOLUME
+            false, // READ_PHONE_NUMBERS
+            false, // REQUEST_INSTALL_PACKAGES
+            false, // ENTER_PICTURE_IN_PICTURE_ON_HIDE
+            false, // INSTANT_APP_START_FOREGROUND
+            false, // ANSWER_PHONE_CALLS
+            false, // OP_RUN_ANY_IN_BACKGROUND
+            false, // OP_CHANGE_WIFI_STATE
+            false, // OP_REQUEST_DELETE_PACKAGES
+            false, // OP_BIND_ACCESSIBILITY_SERVICE
+            false, // ACCEPT_HANDOVER
+            false, // MANAGE_IPSEC_HANDOVERS
+            false, // START_FOREGROUND
+            true, // BLUETOOTH_SCAN
+            false, // USE_BIOMETRIC
+            false, // ACTIVITY_RECOGNITION
+            false, // SMS_FINANCIAL_TRANSACTIONS
+            false, // READ_MEDIA_AUDIO
+            false, // WRITE_MEDIA_AUDIO
+            false, // READ_MEDIA_VIDEO
+            false, // WRITE_MEDIA_VIDEO
+            false, // READ_MEDIA_IMAGES
+            false, // WRITE_MEDIA_IMAGES
+            false, // LEGACY_STORAGE
+            false, // ACCESS_ACCESSIBILITY
+            false, // READ_DEVICE_IDENTIFIERS
+    };
+
+    /**
+     * This specifies the default mode for each operation.
+     */
+    private static int[] sOpDefaultMode = new int[] {
+            AppOpsManager.MODE_ALLOWED, // COARSE_LOCATION
+            AppOpsManager.MODE_ALLOWED, // FINE_LOCATION
+            AppOpsManager.MODE_ALLOWED, // GPS
+            AppOpsManager.MODE_ALLOWED, // VIBRATE
+            AppOpsManager.MODE_ALLOWED, // READ_CONTACTS
+            AppOpsManager.MODE_ALLOWED, // WRITE_CONTACTS
+            AppOpsManager.MODE_ALLOWED, // READ_CALL_LOG
+            AppOpsManager.MODE_ALLOWED, // WRITE_CALL_LOG
+            AppOpsManager.MODE_ALLOWED, // READ_CALENDAR
+            AppOpsManager.MODE_ALLOWED, // WRITE_CALENDAR
+            AppOpsManager.MODE_ALLOWED, // WIFI_SCAN
+            AppOpsManager.MODE_ALLOWED, // POST_NOTIFICATION
+            AppOpsManager.MODE_ALLOWED, // NEIGHBORING_CELLS
+            AppOpsManager.MODE_ALLOWED, // CALL_PHONE
+            AppOpsManager.MODE_ALLOWED, // READ_SMS
+            AppOpsManager.MODE_IGNORED, // WRITE_SMS
+            AppOpsManager.MODE_ALLOWED, // RECEIVE_SMS
+            AppOpsManager.MODE_ALLOWED, // RECEIVE_EMERGENCY_BROADCAST
+            AppOpsManager.MODE_ALLOWED, // RECEIVE_MMS
+            AppOpsManager.MODE_ALLOWED, // RECEIVE_WAP_PUSH
+            AppOpsManager.MODE_ALLOWED, // SEND_SMS
+            AppOpsManager.MODE_ALLOWED, // READ_ICC_SMS
+            AppOpsManager.MODE_ALLOWED, // WRITE_ICC_SMS
+            AppOpsManager.MODE_DEFAULT, // WRITE_SETTINGS
+            getSystemAlertWindowDefault(), // SYSTEM_ALERT_WINDOW
+            AppOpsManager.MODE_ALLOWED, // ACCESS_NOTIFICATIONS
+            AppOpsManager.MODE_ALLOWED, // CAMERA
+            AppOpsManager.MODE_ALLOWED, // RECORD_AUDIO
+            AppOpsManager.MODE_ALLOWED, // PLAY_AUDIO
+            AppOpsManager.MODE_ALLOWED, // READ_CLIPBOARD
+            AppOpsManager.MODE_ALLOWED, // WRITE_CLIPBOARD
+            AppOpsManager.MODE_ALLOWED, // TAKE_MEDIA_BUTTONS
+            AppOpsManager.MODE_ALLOWED, // TAKE_AUDIO_FOCUS
+            AppOpsManager.MODE_ALLOWED, // AUDIO_MASTER_VOLUME
+            AppOpsManager.MODE_ALLOWED, // AUDIO_VOICE_VOLUME
+            AppOpsManager.MODE_ALLOWED, // AUDIO_RING_VOLUME
+            AppOpsManager.MODE_ALLOWED, // AUDIO_MEDIA_VOLUME
+            AppOpsManager.MODE_ALLOWED, // AUDIO_ALARM_VOLUME
+            AppOpsManager.MODE_ALLOWED, // AUDIO_NOTIFICATION_VOLUME
+            AppOpsManager.MODE_ALLOWED, // AUDIO_BLUETOOTH_VOLUME
+            AppOpsManager.MODE_ALLOWED, // WAKE_LOCK
+            AppOpsManager.MODE_ALLOWED, // MONITOR_LOCATION
+            AppOpsManager.MODE_ALLOWED, // MONITOR_HIGH_POWER_LOCATION
+            AppOpsManager.MODE_DEFAULT, // GET_USAGE_STATS
+            AppOpsManager.MODE_ALLOWED, // MUTE_MICROPHONE
+            AppOpsManager.MODE_ALLOWED, // TOAST_WINDOW
+            AppOpsManager.MODE_IGNORED, // PROJECT_MEDIA
+            AppOpsManager.MODE_IGNORED, // ACTIVATE_VPN
+            AppOpsManager.MODE_ALLOWED, // WRITE_WALLPAPER
+            AppOpsManager.MODE_ALLOWED, // ASSIST_STRUCTURE
+            AppOpsManager.MODE_ALLOWED, // ASSIST_SCREENSHOT
+            AppOpsManager.MODE_ALLOWED, // READ_PHONE_STATE
+            AppOpsManager.MODE_ALLOWED, // ADD_VOICEMAIL
+            AppOpsManager.MODE_ALLOWED, // USE_SIP
+            AppOpsManager.MODE_ALLOWED, // PROCESS_OUTGOING_CALLS
+            AppOpsManager.MODE_ALLOWED, // USE_FINGERPRINT
+            AppOpsManager.MODE_ALLOWED, // BODY_SENSORS
+            AppOpsManager.MODE_ALLOWED, // READ_CELL_BROADCASTS
+            AppOpsManager.MODE_ERRORED, // MOCK_LOCATION
+            AppOpsManager.MODE_ALLOWED, // READ_EXTERNAL_STORAGE
+            AppOpsManager.MODE_ALLOWED, // WRITE_EXTERNAL_STORAGE
+            AppOpsManager.MODE_ALLOWED, // TURN_SCREEN_ON
+            AppOpsManager.MODE_ALLOWED, // GET_ACCOUNTS
+            AppOpsManager.MODE_ALLOWED, // RUN_IN_BACKGROUND
+            AppOpsManager.MODE_ALLOWED, // AUDIO_ACCESSIBILITY_VOLUME
+            AppOpsManager.MODE_ALLOWED, // READ_PHONE_NUMBERS
+            AppOpsManager.MODE_DEFAULT, // REQUEST_INSTALL_PACKAGES
+            AppOpsManager.MODE_ALLOWED, // PICTURE_IN_PICTURE
+            AppOpsManager.MODE_DEFAULT, // INSTANT_APP_START_FOREGROUND
+            AppOpsManager.MODE_ALLOWED, // ANSWER_PHONE_CALLS
+            AppOpsManager.MODE_ALLOWED, // RUN_ANY_IN_BACKGROUND
+            AppOpsManager.MODE_ALLOWED, // CHANGE_WIFI_STATE
+            AppOpsManager.MODE_ALLOWED, // REQUEST_DELETE_PACKAGES
+            AppOpsManager.MODE_ALLOWED, // BIND_ACCESSIBILITY_SERVICE
+            AppOpsManager.MODE_ALLOWED, // ACCEPT_HANDOVER
+            AppOpsManager.MODE_ERRORED, // MANAGE_IPSEC_TUNNELS
+            AppOpsManager.MODE_ALLOWED, // START_FOREGROUND
+            AppOpsManager.MODE_ALLOWED, // BLUETOOTH_SCAN
+            AppOpsManager.MODE_ALLOWED, // USE_BIOMETRIC
+            AppOpsManager.MODE_ALLOWED, // ACTIVITY_RECOGNITION
+            AppOpsManager.MODE_DEFAULT, // SMS_FINANCIAL_TRANSACTIONS
+            AppOpsManager.MODE_ALLOWED, // READ_MEDIA_AUDIO
+            AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_AUDIO
+            AppOpsManager.MODE_ALLOWED, // READ_MEDIA_VIDEO
+            AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_VIDEO
+            AppOpsManager.MODE_ALLOWED, // READ_MEDIA_IMAGES
+            AppOpsManager.MODE_ERRORED, // WRITE_MEDIA_IMAGES
+            AppOpsManager.MODE_DEFAULT, // LEGACY_STORAGE
+            AppOpsManager.MODE_ALLOWED, // ACCESS_ACCESSIBILITY
+            AppOpsManager.MODE_ERRORED, // READ_DEVICE_IDENTIFIERS
+    };
+
+    /**
+     * This specifies whether each option is allowed to be reset
+     * when resetting all app preferences.  Disable reset for
+     * app ops that are under strong control of some part of the
+     * system (such as OP_WRITE_SMS, which should be allowed only
+     * for whichever app is selected as the current SMS app).
+     */
+    private static boolean[] sOpDisableReset = new boolean[] {
+            false, // COARSE_LOCATION
+            false, // FINE_LOCATION
+            false, // GPS
+            false, // VIBRATE
+            false, // READ_CONTACTS
+            false, // WRITE_CONTACTS
+            false, // READ_CALL_LOG
+            false, // WRITE_CALL_LOG
+            false, // READ_CALENDAR
+            false, // WRITE_CALENDAR
+            false, // WIFI_SCAN
+            false, // POST_NOTIFICATION
+            false, // NEIGHBORING_CELLS
+            false, // CALL_PHONE
+            true, // READ_SMS
+            true, // WRITE_SMS
+            true, // RECEIVE_SMS
+            false, // RECEIVE_EMERGENCY_BROADCAST
+            false, // RECEIVE_MMS
+            true, // RECEIVE_WAP_PUSH
+            true, // SEND_SMS
+            false, // READ_ICC_SMS
+            false, // WRITE_ICC_SMS
+            false, // WRITE_SETTINGS
+            false, // SYSTEM_ALERT_WINDOW
+            false, // ACCESS_NOTIFICATIONS
+            false, // CAMERA
+            false, // RECORD_AUDIO
+            false, // PLAY_AUDIO
+            false, // READ_CLIPBOARD
+            false, // WRITE_CLIPBOARD
+            false, // TAKE_MEDIA_BUTTONS
+            false, // TAKE_AUDIO_FOCUS
+            false, // AUDIO_MASTER_VOLUME
+            false, // AUDIO_VOICE_VOLUME
+            false, // AUDIO_RING_VOLUME
+            false, // AUDIO_MEDIA_VOLUME
+            false, // AUDIO_ALARM_VOLUME
+            false, // AUDIO_NOTIFICATION_VOLUME
+            false, // AUDIO_BLUETOOTH_VOLUME
+            false, // WAKE_LOCK
+            false, // MONITOR_LOCATION
+            false, // MONITOR_HIGH_POWER_LOCATION
+            false, // GET_USAGE_STATS
+            false, // MUTE_MICROPHONE
+            false, // TOAST_WINDOW
+            false, // PROJECT_MEDIA
+            false, // ACTIVATE_VPN
+            false, // WRITE_WALLPAPER
+            false, // ASSIST_STRUCTURE
+            false, // ASSIST_SCREENSHOT
+            false, // READ_PHONE_STATE
+            false, // ADD_VOICEMAIL
+            false, // USE_SIP
+            false, // PROCESS_OUTGOING_CALLS
+            false, // USE_FINGERPRINT
+            false, // BODY_SENSORS
+            true, // READ_CELL_BROADCASTS
+            false, // MOCK_LOCATION
+            false, // READ_EXTERNAL_STORAGE
+            false, // WRITE_EXTERNAL_STORAGE
+            false, // TURN_SCREEN_ON
+            false, // GET_ACCOUNTS
+            false, // RUN_IN_BACKGROUND
+            false, // AUDIO_ACCESSIBILITY_VOLUME
+            false, // READ_PHONE_NUMBERS
+            false, // REQUEST_INSTALL_PACKAGES
+            false, // PICTURE_IN_PICTURE
+            false, // INSTANT_APP_START_FOREGROUND
+            false, // ANSWER_PHONE_CALLS
+            false, // RUN_ANY_IN_BACKGROUND
+            false, // CHANGE_WIFI_STATE
+            false, // REQUEST_DELETE_PACKAGES
+            false, // BIND_ACCESSIBILITY_SERVICE
+            false, // ACCEPT_HANDOVER
+            false, // MANAGE_IPSEC_TUNNELS
+            false, // START_FOREGROUND
+            false, // BLUETOOTH_SCAN
+            false, // USE_BIOMETRIC
+            false, // ACTIVITY_RECOGNITION
+            false, // SMS_FINANCIAL_TRANSACTIONS
+            false, // READ_MEDIA_AUDIO
+            false, // WRITE_MEDIA_AUDIO
+            false, // READ_MEDIA_VIDEO
+            false, // WRITE_MEDIA_VIDEO
+            false, // READ_MEDIA_IMAGES
+            false, // WRITE_MEDIA_IMAGES
+            false, // LEGACY_STORAGE
+            false, // ACCESS_ACCESSIBILITY
+            false, // READ_DEVICE_IDENTIFIERS
+    };
+
+    /**
+     * Mapping from an app op name to the app op code.
+     */
+    private static HashMap<String, Integer> sOpStrToOp = new HashMap<>();
+
+    /**
+     * Mapping from a permission to the corresponding app op.
+     */
+    private static HashMap<String, Integer> sPermToOp = new HashMap<>();
+
+    static {
+        if (sOpToSwitch.length != _NUM_OP) {
+            throw new IllegalStateException("sOpToSwitch length " + sOpToSwitch.length
+                    + " should be " + _NUM_OP);
+        }
+        if (sOpToString.length != _NUM_OP) {
+            throw new IllegalStateException("sOpToString length " + sOpToString.length
+                    + " should be " + _NUM_OP);
+        }
+        if (sOpNames.length != _NUM_OP) {
+            throw new IllegalStateException("sOpNames length " + sOpNames.length
+                    + " should be " + _NUM_OP);
+        }
+        if (sOpPerms.length != _NUM_OP) {
+            throw new IllegalStateException("sOpPerms length " + sOpPerms.length
+                    + " should be " + _NUM_OP);
+        }
+        if (sOpDefaultMode.length != _NUM_OP) {
+            throw new IllegalStateException("sOpDefaultMode length " + sOpDefaultMode.length
+                    + " should be " + _NUM_OP);
+        }
+        if (sOpDisableReset.length != _NUM_OP) {
+            throw new IllegalStateException("sOpDisableReset length " + sOpDisableReset.length
+                    + " should be " + _NUM_OP);
+        }
+        if (sOpRestrictions.length != _NUM_OP) {
+            throw new IllegalStateException("sOpRestrictions length " + sOpRestrictions.length
+                    + " should be " + _NUM_OP);
+        }
+        if (sOpAllowSystemRestrictionBypass.length != _NUM_OP) {
+            throw new IllegalStateException("sOpAllowSYstemRestrictionsBypass length "
+                    + sOpRestrictions.length + " should be " + _NUM_OP);
+        }
+        for (int i=0; i<_NUM_OP; i++) {
+            if (sOpToString[i] != null) {
+                sOpStrToOp.put(sOpToString[i], i);
+            }
+        }
+        for (int op : RUNTIME_AND_APPOP_PERMISSIONS_OPS) {
+            if (sOpPerms[op] != null) {
+                sPermToOp.put(sOpPerms[op], op);
+            }
+        }
+    }
+
+    /** @hide */
+    public static final String KEY_HISTORICAL_OPS = "historical_ops";
+
+    /** System properties for debug logging of noteOp call sites */
+    private static final String DEBUG_LOGGING_ENABLE_PROP = "appops.logging_enabled";
+    private static final String DEBUG_LOGGING_PACKAGES_PROP = "appops.logging_packages";
+    private static final String DEBUG_LOGGING_OPS_PROP = "appops.logging_ops";
+    private static final String DEBUG_LOGGING_TAG = "AppOpsManager";
+
+    /**
+     * Retrieve the op switch that controls the given operation.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static int opToSwitch(int op) {
+        return sOpToSwitch[op];
+    }
+
+    /**
+     * Retrieve a non-localized name for the operation, for debugging output.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public static String opToName(int op) {
+        if (op == OP_NONE) return "NONE";
+        return op < sOpNames.length ? sOpNames[op] : ("Unknown(" + op + ")");
+    }
+
+    /**
+     * Retrieve a non-localized public name for the operation.
+     *
+     * @hide
+     */
+    public static @NonNull String opToPublicName(int op) {
+        return sOpToString[op];
+    }
+
+    /**
+     * @hide
+     */
+    public static int strDebugOpToOp(String op) {
+        for (int i=0; i<sOpNames.length; i++) {
+            if (sOpNames[i].equals(op)) {
+                return i;
+            }
+        }
+        throw new IllegalArgumentException("Unknown operation string: " + op);
+    }
+
+    /**
+     * Retrieve the permission associated with an operation, or null if there is not one.
+     * @hide
+     */
+    @TestApi
+    public static String opToPermission(int op) {
+        return sOpPerms[op];
+    }
+
+    /**
+     * Retrieve the permission associated with an operation, or null if there is not one.
+     *
+     * @param op The operation name.
+     *
+     * @hide
+     */
+    @Nullable
+    @SystemApi
+    public static String opToPermission(@NonNull String op) {
+        return opToPermission(strOpToOp(op));
+    }
+
+    /**
+     * Retrieve the user restriction associated with an operation, or null if there is not one.
+     * @hide
+     */
+    public static String opToRestriction(int op) {
+        return sOpRestrictions[op];
+    }
+
+    /**
+     * Retrieve the app op code for a permission, or null if there is not one.
+     * This API is intended to be used for mapping runtime or appop permissions
+     * to the corresponding app op.
+     * @hide
+     */
+    @TestApi
+    public static int permissionToOpCode(String permission) {
+        Integer boxedOpCode = sPermToOp.get(permission);
+        return boxedOpCode != null ? boxedOpCode : OP_NONE;
+    }
+
+    /**
+     * Retrieve whether the op allows the system (and system ui) to
+     * bypass the user restriction.
+     * @hide
+     */
+    public static boolean opAllowSystemBypassRestriction(int op) {
+        return sOpAllowSystemRestrictionBypass[op];
+    }
+
+    /**
+     * Retrieve the default mode for the operation.
+     * @hide
+     */
+    public static @Mode int opToDefaultMode(int op) {
+        return sOpDefaultMode[op];
+    }
+
+    /**
+     * Retrieve the default mode for the app op.
+     *
+     * @param appOp The app op name
+     *
+     * @return the default mode for the app op
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static int opToDefaultMode(@NonNull String appOp) {
+        return opToDefaultMode(strOpToOp(appOp));
+    }
+
+    /**
+     * Retrieve the human readable mode.
+     * @hide
+     */
+    public static String modeToName(@Mode int mode) {
+        if (mode >= 0 && mode < MODE_NAMES.length) {
+            return MODE_NAMES[mode];
+        }
+        return "mode=" + mode;
+    }
+
+    /**
+     * Retrieve whether the op allows itself to be reset.
+     * @hide
+     */
+    public static boolean opAllowsReset(int op) {
+        return !sOpDisableReset[op];
+    }
+
+    /**
+     * Class holding all of the operation information associated with an app.
+     * @hide
+     */
+    @SystemApi
+    public static final class PackageOps implements Parcelable {
+        private final String mPackageName;
+        private final int mUid;
+        private final List<OpEntry> mEntries;
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public PackageOps(String packageName, int uid, List<OpEntry> entries) {
+            mPackageName = packageName;
+            mUid = uid;
+            mEntries = entries;
+        }
+
+        /**
+         * @return The name of the package.
+         */
+        public @NonNull String getPackageName() {
+            return mPackageName;
+        }
+
+        /**
+         * @return The uid of the package.
+         */
+        public int getUid() {
+            return mUid;
+        }
+
+        /**
+         * @return The ops of the package.
+         */
+        public @NonNull List<OpEntry> getOps() {
+            return mEntries;
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(mPackageName);
+            dest.writeInt(mUid);
+            dest.writeInt(mEntries.size());
+            for (int i=0; i<mEntries.size(); i++) {
+                mEntries.get(i).writeToParcel(dest, flags);
+            }
+        }
+
+        PackageOps(Parcel source) {
+            mPackageName = source.readString();
+            mUid = source.readInt();
+            mEntries = new ArrayList<OpEntry>();
+            final int N = source.readInt();
+            for (int i=0; i<N; i++) {
+                mEntries.add(OpEntry.CREATOR.createFromParcel(source));
+            }
+        }
+
+        public static final @android.annotation.NonNull Creator<PackageOps> CREATOR = new Creator<PackageOps>() {
+            @Override public PackageOps createFromParcel(Parcel source) {
+                return new PackageOps(source);
+            }
+
+            @Override public PackageOps[] newArray(int size) {
+                return new PackageOps[size];
+            }
+        };
+    }
+
+    /**
+     * Class holding the information about one unique operation of an application.
+     * @hide
+     */
+    @TestApi
+    @Immutable
+    @SystemApi
+    public static final class OpEntry implements Parcelable {
+        private final int mOp;
+        private final boolean mRunning;
+        private final @Mode int mMode;
+        private final @Nullable LongSparseLongArray mAccessTimes;
+        private final @Nullable LongSparseLongArray mRejectTimes;
+        private final @Nullable LongSparseLongArray mDurations;
+        private final @Nullable LongSparseLongArray mProxyUids;
+        private final @Nullable LongSparseArray<String> mProxyPackageNames;
+
+        /**
+         * @hide
+         */
+        public OpEntry(int op, boolean running, @Mode int mode,
+                @Nullable LongSparseLongArray accessTimes, @Nullable LongSparseLongArray rejectTimes,
+                @Nullable LongSparseLongArray durations, @Nullable LongSparseLongArray proxyUids,
+                @Nullable LongSparseArray<String> proxyPackageNames) {
+            mOp = op;
+            mRunning = running;
+            mMode = mode;
+            mAccessTimes = accessTimes;
+            mRejectTimes = rejectTimes;
+            mDurations = durations;
+            mProxyUids = proxyUids;
+            mProxyPackageNames = proxyPackageNames;
+        }
+
+        /**
+         * @hide
+         */
+        public OpEntry(int op, @Mode int mode) {
+            mOp = op;
+            mMode = mode;
+            mRunning = false;
+            mAccessTimes = null;
+            mRejectTimes = null;
+            mDurations = null;
+            mProxyUids = null;
+            mProxyPackageNames = null;
+        }
+
+        /**
+         * Returns all keys for which we have mapped state in any of the data buckets -
+         * access time, reject time, duration.
+         * @hide */
+        public @Nullable LongSparseArray<Object> collectKeys() {
+            LongSparseArray<Object> result = AppOpsManager.collectKeys(mAccessTimes, null);
+            result = AppOpsManager.collectKeys(mRejectTimes, result);
+            result = AppOpsManager.collectKeys(mDurations, result);
+            return result;
+        }
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public int getOp() {
+            return mOp;
+        }
+
+        /**
+         * @return This entry's op string name, such as {@link #OPSTR_COARSE_LOCATION}.
+         */
+        public @NonNull String getOpStr() {
+            return sOpToString[mOp];
+        }
+
+        /**
+         * @return this entry's current mode, such as {@link #MODE_ALLOWED}.
+         */
+        public @Mode int getMode() {
+            return mMode;
+        }
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public long getTime() {
+            return getLastAccessTime(OP_FLAGS_ALL);
+        }
+
+        /**
+         * Return the last wall clock time in milliseconds this op was accessed.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return the last access time in milliseconds since
+         * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         *
+         * @see #getLastAccessForegroundTime(int)
+         * @see #getLastAccessBackgroundTime(int)
+         * @see #getLastAccessTime(int, int, int)
+         */
+        public long getLastAccessTime(@OpFlags int flags) {
+            return maxForFlagsInStates(mAccessTimes, MAX_PRIORITY_UID_STATE,
+                    MIN_PRIORITY_UID_STATE, flags);
+        }
+
+        /**
+         * Return the last wall clock time in milliseconds this op was accessed
+         * by the app while in the foreground.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return the last foreground access time in milliseconds since
+         * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         *
+         * @see #getLastAccessBackgroundTime(int)
+         * @see #getLastAccessTime(int)
+         * @see #getLastAccessTime(int, int, int)
+         */
+        public long getLastAccessForegroundTime(@OpFlags int flags) {
+            return maxForFlagsInStates(mAccessTimes, MAX_PRIORITY_UID_STATE,
+                    resolveFirstUnrestrictedUidState(mOp), flags);
+        }
+
+        /**
+         * Return the last wall clock time in milliseconds this op was accessed
+         * by the app while in the background.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return the last foreground access time in milliseconds since
+         * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         *
+         * @see #getLastAccessForegroundTime(int)
+         * @see #getLastAccessTime(int)
+         * @see #getLastAccessTime(int, int, int)
+         */
+        public long getLastAccessBackgroundTime(@OpFlags int flags) {
+            return maxForFlagsInStates(mAccessTimes, resolveLastRestrictedUidState(mOp),
+                    MIN_PRIORITY_UID_STATE, flags);
+        }
+
+        /**
+         * Return the last wall clock time  in milliseconds this op was accessed
+         * by the app for a given range of UID states.
+         *
+         * @param fromUidState The UID state for which to query. Could be one of
+         * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
+         * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
+         * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
+         * @param toUidState The UID state for which to query.
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         *
+         * @return the last foreground access time in milliseconds since
+         * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         *
+         * @see #getLastAccessForegroundTime(int)
+         * @see #getLastAccessBackgroundTime(int)
+         * @see #getLastAccessTime(int)
+         */
+        public long getLastAccessTime(@UidState int fromUidState, @UidState int toUidState,
+                @OpFlags int flags) {
+            return maxForFlagsInStates(mAccessTimes, fromUidState, toUidState, flags);
+        }
+
+        /**
+         * @hide
+         */
+        @UnsupportedAppUsage
+        public long getRejectTime() {
+            return getLastRejectTime(OP_FLAGS_ALL);
+        }
+
+        /**
+         * Return the last wall clock time in milliseconds the app made an attempt
+         * to access this op but was rejected.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return the last reject time in milliseconds since
+         * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         *
+         * @see #getLastRejectBackgroundTime(int)
+         * @see #getLastRejectForegroundTime(int)
+         * @see #getLastRejectTime(int, int, int)
+         */
+        public long getLastRejectTime(@OpFlags int flags) {
+            return maxForFlagsInStates(mRejectTimes, MAX_PRIORITY_UID_STATE,
+                    MIN_PRIORITY_UID_STATE, flags);
+        }
+
+        /**
+         * Return the last wall clock time in milliseconds the app made an attempt
+         * to access this op while in the foreground but was rejected.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return the last foreground reject time in milliseconds since
+         * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         *
+         * @see #getLastRejectBackgroundTime(int)
+         * @see #getLastRejectTime(int, int, int)
+         * @see #getLastRejectTime(int)
+         */
+        public long getLastRejectForegroundTime(@OpFlags int flags) {
+            return maxForFlagsInStates(mRejectTimes, MAX_PRIORITY_UID_STATE,
+                    resolveFirstUnrestrictedUidState(mOp), flags);
+        }
+
+        /**
+         * Return the last wall clock time in milliseconds the app made an attempt
+         * to access this op while in the background but was rejected.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return the last background reject time in milliseconds since
+         * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         *
+         * @see #getLastRejectForegroundTime(int)
+         * @see #getLastRejectTime(int, int, int)
+         * @see #getLastRejectTime(int)
+         */
+        public long getLastRejectBackgroundTime(@OpFlags int flags) {
+            return maxForFlagsInStates(mRejectTimes, resolveLastRestrictedUidState(mOp),
+                    MIN_PRIORITY_UID_STATE, flags);
+        }
+
+        /**
+         * Return the last wall clock time state in milliseconds the app made an
+         * attempt to access this op for a given range of UID states.
+         *
+         * @param fromUidState The UID state from which to query. Could be one of
+         * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
+         * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
+         * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
+         * @param toUidState The UID state to which to query.
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return the last foreground access time in milliseconds since
+         * epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         *
+         * @see #getLastRejectForegroundTime(int)
+         * @see #getLastRejectBackgroundTime(int)
+         * @see #getLastRejectTime(int)
+         */
+        public long getLastRejectTime(@UidState int fromUidState, @UidState int toUidState,
+                @OpFlags int flags) {
+            return maxForFlagsInStates(mRejectTimes, fromUidState, toUidState, flags);
+        }
+
+        /**
+         * @return Whether the operation is running.
+         */
+        public boolean isRunning() {
+            return mRunning;
+        }
+
+        /**
+         * @return The duration of the operation in milliseconds. The duration is in wall time.
+         */
+        public long getDuration() {
+            return getLastDuration(MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
+        }
+
+        /**
+         * Return the duration in milliseconds the app accessed this op while
+         * in the foreground. The duration is in wall time.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return the foreground access duration in milliseconds.
+         *
+         * @see #getLastBackgroundDuration(int)
+         * @see #getLastDuration(int, int, int)
+         */
+        public long getLastForegroundDuration(@OpFlags int flags) {
+            return sumForFlagsInStates(mDurations, MAX_PRIORITY_UID_STATE,
+                    resolveFirstUnrestrictedUidState(mOp), flags);
+        }
+
+        /**
+         * Return the duration in milliseconds the app accessed this op while
+         * in the background. The duration is in wall time.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return the background access duration in milliseconds.
+         *
+         * @see #getLastForegroundDuration(int)
+         * @see #getLastDuration(int, int, int)
+         */
+        public long getLastBackgroundDuration(@OpFlags int flags) {
+            return sumForFlagsInStates(mDurations, resolveLastRestrictedUidState(mOp),
+                    MIN_PRIORITY_UID_STATE, flags);
+        }
+
+        /**
+         * Return the duration in milliseconds the app accessed this op for
+         * a given range of UID states. The duration is in wall time.
+         *
+         * @param fromUidState The UID state for which to query. Could be one of
+         * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
+         * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
+         * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
+         * @param toUidState The UID state for which to query.
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return the access duration in milliseconds.
+         */
+        public long getLastDuration(@UidState int fromUidState, @UidState int toUidState,
+                @OpFlags int flags) {
+            return sumForFlagsInStates(mDurations, fromUidState, toUidState, flags);
+        }
+
+        /**
+         * Gets the UID of the app that performed the op on behalf of this app and
+         * as a result blamed the op on this app or {@link Process#INVALID_UID} if
+         * there is no proxy.
+         *
+         * @return The proxy UID.
+         */
+        public int getProxyUid() {
+            return (int) findFirstNonNegativeForFlagsInStates(mDurations,
+                    MAX_PRIORITY_UID_STATE, MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
+        }
+
+        /**
+         * Gets the UID of the app that performed the op on behalf of this app and
+         * as a result blamed the op on this app or {@link Process#INVALID_UID} if
+         * there is no proxy.
+         *
+         * @param uidState The UID state for which to query. Could be one of
+         * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
+         * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
+         * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         *
+         * @return The proxy UID.
+         */
+        public int getProxyUid(@UidState int uidState, @OpFlags int flags) {
+            return (int) findFirstNonNegativeForFlagsInStates(mDurations,
+                    uidState, uidState, flags);
+        }
+
+        /**
+         * Gets the package name of the app that performed the op on behalf of this
+         * app and as a result blamed the op on this app or {@code null}
+         * if there is no proxy.
+         *
+         * @return The proxy package name.
+         */
+        public @Nullable String getProxyPackageName() {
+            return findFirstNonNullForFlagsInStates(mProxyPackageNames, MAX_PRIORITY_UID_STATE,
+                    MIN_PRIORITY_UID_STATE, OP_FLAGS_ALL);
+        }
+
+        /**
+         * Gets the package name of the app that performed the op on behalf of this
+         * app and as a result blamed the op on this app for a UID state or
+         * {@code null} if there is no proxy.
+         *
+         * @param uidState The UID state for which to query. Could be one of
+         * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
+         * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
+         * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return The proxy package name.
+         */
+        public @Nullable String getProxyPackageName(@UidState int uidState, @OpFlags int flags) {
+            return findFirstNonNullForFlagsInStates(mProxyPackageNames, uidState, uidState, flags);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mOp);
+            dest.writeInt(mMode);
+            dest.writeBoolean(mRunning);
+            writeLongSparseLongArrayToParcel(mAccessTimes, dest);
+            writeLongSparseLongArrayToParcel(mRejectTimes, dest);
+            writeLongSparseLongArrayToParcel(mDurations, dest);
+            writeLongSparseLongArrayToParcel(mProxyUids, dest);
+            writeLongSparseStringArrayToParcel(mProxyPackageNames, dest);
+        }
+
+        OpEntry(Parcel source) {
+            mOp = source.readInt();
+            mMode = source.readInt();
+            mRunning = source.readBoolean();
+            mAccessTimes = readLongSparseLongArrayFromParcel(source);
+            mRejectTimes = readLongSparseLongArrayFromParcel(source);
+            mDurations = readLongSparseLongArrayFromParcel(source);
+            mProxyUids = readLongSparseLongArrayFromParcel(source);
+            mProxyPackageNames = readLongSparseStringArrayFromParcel(source);
+        }
+
+        public static final @android.annotation.NonNull Creator<OpEntry> CREATOR = new Creator<OpEntry>() {
+            @Override public OpEntry createFromParcel(Parcel source) {
+                return new OpEntry(source);
+            }
+
+            @Override public OpEntry[] newArray(int size) {
+                return new OpEntry[size];
+            }
+        };
+    }
+
+    /** @hide */
+    public interface HistoricalOpsVisitor {
+        void visitHistoricalOps(@NonNull HistoricalOps ops);
+        void visitHistoricalUidOps(@NonNull HistoricalUidOps ops);
+        void visitHistoricalPackageOps(@NonNull HistoricalPackageOps ops);
+        void visitHistoricalOp(@NonNull HistoricalOp ops);
+    }
+
+    /**
+     * Request for getting historical app op usage. The request acts
+     * as a filtering criteria when querying historical op usage.
+     *
+     * @hide
+     */
+    @Immutable
+    @TestApi
+    @SystemApi
+    public static final class HistoricalOpsRequest {
+        private final int mUid;
+        private final @Nullable String mPackageName;
+        private final @Nullable List<String> mOpNames;
+        private final long mBeginTimeMillis;
+        private final long mEndTimeMillis;
+        private final @OpFlags int mFlags;
+
+        private HistoricalOpsRequest(int uid, @Nullable String packageName,
+                @Nullable List<String> opNames, long beginTimeMillis, long endTimeMillis,
+                @OpFlags int flags) {
+            mUid = uid;
+            mPackageName = packageName;
+            mOpNames = opNames;
+            mBeginTimeMillis = beginTimeMillis;
+            mEndTimeMillis = endTimeMillis;
+            mFlags = flags;
+        }
+
+        /**
+         * Builder for creating a {@link HistoricalOpsRequest}.
+         *
+         * @hide
+         */
+        @TestApi
+        @SystemApi
+        public static final class Builder {
+            private int mUid = Process.INVALID_UID;
+            private @Nullable String mPackageName;
+            private @Nullable List<String> mOpNames;
+            private final long mBeginTimeMillis;
+            private final long mEndTimeMillis;
+            private @OpFlags int mFlags = OP_FLAGS_ALL;
+
+            /**
+             * Creates a new builder.
+             *
+             * @param beginTimeMillis The beginning of the interval in milliseconds since
+             *     epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be non
+             *     negative.
+             * @param endTimeMillis The end of the interval in milliseconds since
+             *     epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian). Must be after
+             *     {@code beginTimeMillis}. Pass {@link Long#MAX_VALUE} to get the most recent
+             *     history including ops that happen while this call is in flight.
+             */
+            public Builder(long beginTimeMillis, long endTimeMillis) {
+                Preconditions.checkArgument(beginTimeMillis >= 0 && beginTimeMillis < endTimeMillis,
+                        "beginTimeMillis must be non negative and lesser than endTimeMillis");
+                mBeginTimeMillis = beginTimeMillis;
+                mEndTimeMillis = endTimeMillis;
+            }
+
+            /**
+             * Sets the UID to query for.
+             *
+             * @param uid The uid. Pass {@link android.os.Process#INVALID_UID} for any uid.
+             * @return This builder.
+             */
+            public @NonNull Builder setUid(int uid) {
+                Preconditions.checkArgument(uid == Process.INVALID_UID || uid >= 0,
+                        "uid must be " + Process.INVALID_UID + " or non negative");
+                mUid = uid;
+                return this;
+            }
+
+            /**
+             * Sets the package to query for.
+             *
+             * @param packageName The package name. <code>Null</code> for any package.
+             * @return This builder.
+             */
+            public @NonNull Builder setPackageName(@Nullable String packageName) {
+                mPackageName = packageName;
+                return this;
+            }
+
+            /**
+             * Sets the op names to query for.
+             *
+             * @param opNames The op names. <code>Null</code> for any op.
+             * @return This builder.
+             */
+            public @NonNull Builder setOpNames(@Nullable List<String> opNames) {
+                if (opNames != null) {
+                    final int opCount = opNames.size();
+                    for (int i = 0; i < opCount; i++) {
+                        Preconditions.checkArgument(AppOpsManager.strOpToOp(
+                                opNames.get(i)) != AppOpsManager.OP_NONE);
+                    }
+                }
+                mOpNames = opNames;
+                return this;
+            }
+
+            /**
+             * Sets the op flags to query for. The flags specify the type of
+             * op data being queried.
+             *
+             * @param flags The flags which are any combination of
+             * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+             * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+             * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+             * for any flag.
+             * @return This builder.
+             */
+            public @NonNull Builder setFlags(@OpFlags int flags) {
+                Preconditions.checkFlagsArgument(flags, OP_FLAGS_ALL);
+                mFlags = flags;
+                return this;
+            }
+
+            /**
+             * @return a new {@link HistoricalOpsRequest}.
+             */
+            public @NonNull HistoricalOpsRequest build() {
+                return new HistoricalOpsRequest(mUid, mPackageName, mOpNames,
+                        mBeginTimeMillis, mEndTimeMillis, mFlags);
+            }
+        }
+    }
+
+    /**
+     * This class represents historical app op state of all UIDs for a given time interval.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final class HistoricalOps implements Parcelable {
+        private long mBeginTimeMillis;
+        private long mEndTimeMillis;
+        private @Nullable SparseArray<HistoricalUidOps> mHistoricalUidOps;
+
+        /** @hide */
+        @TestApi
+        public HistoricalOps(long beginTimeMillis, long endTimeMillis) {
+            Preconditions.checkState(beginTimeMillis <= endTimeMillis);
+            mBeginTimeMillis = beginTimeMillis;
+            mEndTimeMillis = endTimeMillis;
+        }
+
+        /** @hide */
+        public HistoricalOps(@NonNull HistoricalOps other) {
+            mBeginTimeMillis = other.mBeginTimeMillis;
+            mEndTimeMillis = other.mEndTimeMillis;
+            Preconditions.checkState(mBeginTimeMillis <= mEndTimeMillis);
+            if (other.mHistoricalUidOps != null) {
+                final int opCount = other.getUidCount();
+                for (int i = 0; i < opCount; i++) {
+                    final HistoricalUidOps origOps = other.getUidOpsAt(i);
+                    final HistoricalUidOps clonedOps = new HistoricalUidOps(origOps);
+                    if (mHistoricalUidOps == null) {
+                        mHistoricalUidOps = new SparseArray<>(opCount);
+                    }
+                    mHistoricalUidOps.put(clonedOps.getUid(), clonedOps);
+                }
+            }
+        }
+
+        private HistoricalOps(Parcel parcel) {
+            mBeginTimeMillis = parcel.readLong();
+            mEndTimeMillis = parcel.readLong();
+            final int[] uids = parcel.createIntArray();
+            if (!ArrayUtils.isEmpty(uids)) {
+                final ParceledListSlice<HistoricalUidOps> listSlice = parcel.readParcelable(
+                        HistoricalOps.class.getClassLoader());
+                final List<HistoricalUidOps> uidOps = (listSlice != null)
+                        ? listSlice.getList() : null;
+                if (uidOps == null) {
+                    return;
+                }
+                for (int i = 0; i < uids.length; i++) {
+                    if (mHistoricalUidOps == null) {
+                        mHistoricalUidOps = new SparseArray<>();
+                    }
+                    mHistoricalUidOps.put(uids[i], uidOps.get(i));
+                }
+            }
+        }
+
+        /**
+         * Splice a piece from the beginning of these ops.
+         *
+         * @param splicePoint The fraction of the data to be spliced off.
+         *
+         * @hide
+         */
+        public @NonNull HistoricalOps spliceFromBeginning(double splicePoint) {
+            return splice(splicePoint, true);
+        }
+
+        /**
+         * Splice a piece from the end of these ops.
+         *
+         * @param fractionToRemove The fraction of the data to be spliced off.
+         *
+         * @hide
+         */
+        public @NonNull HistoricalOps spliceFromEnd(double fractionToRemove) {
+            return splice(fractionToRemove, false);
+        }
+
+        /**
+         * Splice a piece from the beginning or end of these ops.
+         *
+         * @param fractionToRemove The fraction of the data to be spliced off.
+         * @param beginning Whether to splice off the beginning or the end.
+         *
+         * @return The spliced off part.
+         *
+         * @hide
+         */
+        private @Nullable HistoricalOps splice(double fractionToRemove, boolean beginning) {
+            final long spliceBeginTimeMills;
+            final long spliceEndTimeMills;
+            if (beginning) {
+                spliceBeginTimeMills = mBeginTimeMillis;
+                spliceEndTimeMills = (long) (mBeginTimeMillis
+                        + getDurationMillis() * fractionToRemove);
+                mBeginTimeMillis = spliceEndTimeMills;
+            } else {
+                spliceBeginTimeMills = (long) (mEndTimeMillis
+                        - getDurationMillis() * fractionToRemove);
+                spliceEndTimeMills = mEndTimeMillis;
+                mEndTimeMillis = spliceBeginTimeMills;
+            }
+
+            HistoricalOps splice = null;
+            final int uidCount = getUidCount();
+            for (int i = 0; i < uidCount; i++) {
+                final HistoricalUidOps origOps = getUidOpsAt(i);
+                final HistoricalUidOps spliceOps = origOps.splice(fractionToRemove);
+                if (spliceOps != null) {
+                    if (splice == null) {
+                        splice = new HistoricalOps(spliceBeginTimeMills, spliceEndTimeMills);
+                    }
+                    if (splice.mHistoricalUidOps == null) {
+                        splice.mHistoricalUidOps = new SparseArray<>();
+                    }
+                    splice.mHistoricalUidOps.put(spliceOps.getUid(), spliceOps);
+                }
+            }
+            return splice;
+        }
+
+        /**
+         * Merge the passed ops into the current ones. The time interval is a
+         * union of the current and passed in one and the passed in data is
+         * folded into the data of this instance.
+         *
+         * @hide
+         */
+        public void merge(@NonNull HistoricalOps other) {
+            mBeginTimeMillis = Math.min(mBeginTimeMillis, other.mBeginTimeMillis);
+            mEndTimeMillis = Math.max(mEndTimeMillis, other.mEndTimeMillis);
+            final int uidCount = other.getUidCount();
+            for (int i = 0; i < uidCount; i++) {
+                final HistoricalUidOps otherUidOps = other.getUidOpsAt(i);
+                final HistoricalUidOps thisUidOps = getUidOps(otherUidOps.getUid());
+                if (thisUidOps != null) {
+                    thisUidOps.merge(otherUidOps);
+                } else {
+                    if (mHistoricalUidOps == null) {
+                        mHistoricalUidOps = new SparseArray<>();
+                    }
+                    mHistoricalUidOps.put(otherUidOps.getUid(), otherUidOps);
+                }
+            }
+        }
+
+        /**
+         * AppPermissionUsage the ops to leave only the data we filter for.
+         *
+         * @param uid Uid to filter for or {@link android.os.Process#INCIDENTD_UID} for all.
+         * @param packageName Package to filter for or null for all.
+         * @param opNames Ops to filter for or null for all.
+         * @param beginTimeMillis The begin time to filter for or {@link Long#MIN_VALUE} for all.
+         * @param endTimeMillis The end time to filter for or {@link Long#MAX_VALUE} for all.
+         *
+         * @hide
+         */
+        public void filter(int uid, @Nullable String packageName, @Nullable String[] opNames,
+                long beginTimeMillis, long endTimeMillis) {
+            final long durationMillis = getDurationMillis();
+            mBeginTimeMillis = Math.max(mBeginTimeMillis, beginTimeMillis);
+            mEndTimeMillis = Math.min(mEndTimeMillis, endTimeMillis);
+            final double scaleFactor = Math.min((double) (endTimeMillis - beginTimeMillis)
+                    / (double) durationMillis, 1);
+            final int uidCount = getUidCount();
+            for (int i = uidCount - 1; i >= 0; i--) {
+                final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
+                if (uid != Process.INVALID_UID && uid != uidOp.getUid()) {
+                    mHistoricalUidOps.removeAt(i);
+                } else {
+                    uidOp.filter(packageName, opNames, scaleFactor);
+                }
+            }
+        }
+
+        /** @hide */
+        public boolean isEmpty() {
+            if (getBeginTimeMillis() >= getEndTimeMillis()) {
+                return true;
+            }
+            final int uidCount = getUidCount();
+            for (int i = uidCount - 1; i >= 0; i--) {
+                final HistoricalUidOps uidOp = mHistoricalUidOps.valueAt(i);
+                if (!uidOp.isEmpty()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        /** @hide */
+        public long getDurationMillis() {
+            return mEndTimeMillis - mBeginTimeMillis;
+        }
+
+        /** @hide */
+        @TestApi
+        public void increaseAccessCount(int opCode, int uid, @NonNull String packageName,
+                @UidState int uidState,  @OpFlags int flags, long increment) {
+            getOrCreateHistoricalUidOps(uid).increaseAccessCount(opCode,
+                    packageName, uidState, flags, increment);
+        }
+
+        /** @hide */
+        @TestApi
+        public void increaseRejectCount(int opCode, int uid, @NonNull String packageName,
+                @UidState int uidState, @OpFlags int flags, long increment) {
+            getOrCreateHistoricalUidOps(uid).increaseRejectCount(opCode,
+                    packageName, uidState, flags, increment);
+        }
+
+        /** @hide */
+        @TestApi
+        public void increaseAccessDuration(int opCode, int uid, @NonNull String packageName,
+                @UidState int uidState, @OpFlags int flags, long increment) {
+            getOrCreateHistoricalUidOps(uid).increaseAccessDuration(opCode,
+                    packageName, uidState, flags, increment);
+        }
+
+        /** @hide */
+        @TestApi
+        public void offsetBeginAndEndTime(long offsetMillis) {
+            mBeginTimeMillis += offsetMillis;
+            mEndTimeMillis += offsetMillis;
+        }
+
+        /** @hide */
+        public void setBeginAndEndTime(long beginTimeMillis, long endTimeMillis) {
+            mBeginTimeMillis = beginTimeMillis;
+            mEndTimeMillis = endTimeMillis;
+        }
+
+        /** @hide */
+        public void setBeginTime(long beginTimeMillis) {
+            mBeginTimeMillis = beginTimeMillis;
+        }
+
+        /** @hide */
+        public void setEndTime(long endTimeMillis) {
+            mEndTimeMillis = endTimeMillis;
+        }
+
+        /**
+         * @return The beginning of the interval in milliseconds since
+         *    epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         */
+        public long getBeginTimeMillis() {
+            return mBeginTimeMillis;
+        }
+
+        /**
+         * @return The end of the interval in milliseconds since
+         *    epoch start (January 1, 1970 00:00:00.000 GMT - Gregorian).
+         */
+        public long getEndTimeMillis() {
+            return mEndTimeMillis;
+        }
+
+        /**
+         * Gets number of UIDs with historical ops.
+         *
+         * @return The number of UIDs with historical ops.
+         *
+         * @see #getUidOpsAt(int)
+         */
+        public @IntRange(from = 0) int getUidCount() {
+            if (mHistoricalUidOps == null) {
+                return 0;
+            }
+            return mHistoricalUidOps.size();
+        }
+
+        /**
+         * Gets the historical UID ops at a given index.
+         *
+         * @param index The index.
+         *
+         * @return The historical UID ops at the given index.
+         *
+         * @see #getUidCount()
+         */
+        public @NonNull HistoricalUidOps getUidOpsAt(@IntRange(from = 0) int index) {
+            if (mHistoricalUidOps == null) {
+                throw new IndexOutOfBoundsException();
+            }
+            return mHistoricalUidOps.valueAt(index);
+        }
+
+        /**
+         * Gets the historical UID ops for a given UID.
+         *
+         * @param uid The UID.
+         *
+         * @return The historical ops for the UID.
+         */
+        public @Nullable HistoricalUidOps getUidOps(int uid) {
+            if (mHistoricalUidOps == null) {
+                return null;
+            }
+            return mHistoricalUidOps.get(uid);
+        }
+
+        /** @hide */
+        public void clearHistory(int uid, @NonNull String packageName) {
+            HistoricalUidOps historicalUidOps = getOrCreateHistoricalUidOps(uid);
+            historicalUidOps.clearHistory(packageName);
+            if (historicalUidOps.isEmpty()) {
+                mHistoricalUidOps.remove(uid);
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeLong(mBeginTimeMillis);
+            parcel.writeLong(mEndTimeMillis);
+            if (mHistoricalUidOps != null) {
+                final int uidCount = mHistoricalUidOps.size();
+                parcel.writeInt(uidCount);
+                for (int i = 0; i < uidCount; i++) {
+                    parcel.writeInt(mHistoricalUidOps.keyAt(i));
+                }
+                final List<HistoricalUidOps> opsList = new ArrayList<>(uidCount);
+                for (int i = 0; i < uidCount; i++) {
+                    opsList.add(mHistoricalUidOps.valueAt(i));
+                }
+                parcel.writeParcelable(new ParceledListSlice<>(opsList), flags);
+            } else {
+                parcel.writeInt(-1);
+            }
+        }
+
+        /**
+         * Accepts a visitor to traverse the ops tree.
+         *
+         * @param visitor The visitor.
+         *
+         * @hide
+         */
+        public void accept(@NonNull HistoricalOpsVisitor visitor) {
+            visitor.visitHistoricalOps(this);
+            final int uidCount = getUidCount();
+            for (int i = 0; i < uidCount; i++) {
+                getUidOpsAt(i).accept(visitor);
+            }
+        }
+
+        private @NonNull HistoricalUidOps getOrCreateHistoricalUidOps(int uid) {
+            if (mHistoricalUidOps == null) {
+                mHistoricalUidOps = new SparseArray<>();
+            }
+            HistoricalUidOps historicalUidOp = mHistoricalUidOps.get(uid);
+            if (historicalUidOp == null) {
+                historicalUidOp = new HistoricalUidOps(uid);
+                mHistoricalUidOps.put(uid, historicalUidOp);
+            }
+            return historicalUidOp;
+        }
+
+        /**
+         * @return Rounded value up at the 0.5 boundary.
+         *
+         * @hide
+         */
+        public static double round(double value) {
+            final BigDecimal decimalScale = new BigDecimal(value);
+            return decimalScale.setScale(0, RoundingMode.HALF_UP).doubleValue();
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final HistoricalOps other = (HistoricalOps) obj;
+            if (mBeginTimeMillis != other.mBeginTimeMillis) {
+                return false;
+            }
+            if (mEndTimeMillis != other.mEndTimeMillis) {
+                return false;
+            }
+            if (mHistoricalUidOps == null) {
+                if (other.mHistoricalUidOps != null) {
+                    return false;
+                }
+            } else if (!mHistoricalUidOps.equals(other.mHistoricalUidOps)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = (int) (mBeginTimeMillis ^ (mBeginTimeMillis >>> 32));
+            result = 31 * result + mHistoricalUidOps.hashCode();
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return getClass().getSimpleName() + "[from:"
+                    + mBeginTimeMillis + " to:" + mEndTimeMillis + "]";
+        }
+
+        public static final @android.annotation.NonNull Creator<HistoricalOps> CREATOR = new Creator<HistoricalOps>() {
+            @Override
+            public @NonNull HistoricalOps createFromParcel(@NonNull Parcel parcel) {
+                return new HistoricalOps(parcel);
+            }
+
+            @Override
+            public @NonNull HistoricalOps[] newArray(int size) {
+                return new HistoricalOps[size];
+            }
+        };
+    }
+
+    /**
+     * This class represents historical app op state for a UID.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final class HistoricalUidOps implements Parcelable {
+        private final int mUid;
+        private @Nullable ArrayMap<String, HistoricalPackageOps> mHistoricalPackageOps;
+
+        /** @hide */
+        public HistoricalUidOps(int uid) {
+            mUid = uid;
+        }
+
+        private HistoricalUidOps(@NonNull HistoricalUidOps other) {
+            mUid = other.mUid;
+            final int opCount = other.getPackageCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalPackageOps origOps = other.getPackageOpsAt(i);
+                final HistoricalPackageOps cloneOps = new HistoricalPackageOps(origOps);
+                if (mHistoricalPackageOps == null) {
+                    mHistoricalPackageOps = new ArrayMap<>(opCount);
+                }
+                mHistoricalPackageOps.put(cloneOps.getPackageName(), cloneOps);
+            }
+        }
+
+        private HistoricalUidOps(@NonNull Parcel parcel) {
+            // No arg check since we always read from a trusted source.
+            mUid = parcel.readInt();
+            mHistoricalPackageOps = parcel.createTypedArrayMap(HistoricalPackageOps.CREATOR);
+        }
+
+        private @Nullable HistoricalUidOps splice(double fractionToRemove) {
+            HistoricalUidOps splice = null;
+            final int packageCount = getPackageCount();
+            for (int i = 0; i < packageCount; i++) {
+                final HistoricalPackageOps origOps = getPackageOpsAt(i);
+                final HistoricalPackageOps spliceOps = origOps.splice(fractionToRemove);
+                if (spliceOps != null) {
+                    if (splice == null) {
+                        splice = new HistoricalUidOps(mUid);
+                    }
+                    if (splice.mHistoricalPackageOps == null) {
+                        splice.mHistoricalPackageOps = new ArrayMap<>();
+                    }
+                    splice.mHistoricalPackageOps.put(spliceOps.getPackageName(), spliceOps);
+                }
+            }
+            return splice;
+        }
+
+        private void merge(@NonNull HistoricalUidOps other) {
+            final int packageCount = other.getPackageCount();
+            for (int i = 0; i < packageCount; i++) {
+                final HistoricalPackageOps otherPackageOps = other.getPackageOpsAt(i);
+                final HistoricalPackageOps thisPackageOps = getPackageOps(
+                        otherPackageOps.getPackageName());
+                if (thisPackageOps != null) {
+                    thisPackageOps.merge(otherPackageOps);
+                } else {
+                    if (mHistoricalPackageOps == null) {
+                        mHistoricalPackageOps = new ArrayMap<>();
+                    }
+                    mHistoricalPackageOps.put(otherPackageOps.getPackageName(), otherPackageOps);
+                }
+            }
+        }
+
+        private void filter(@Nullable String packageName, @Nullable String[] opNames,
+                double fractionToRemove) {
+            final int packageCount = getPackageCount();
+            for (int i = packageCount - 1; i >= 0; i--) {
+                final HistoricalPackageOps packageOps = getPackageOpsAt(i);
+                if (packageName != null && !packageName.equals(packageOps.getPackageName())) {
+                    mHistoricalPackageOps.removeAt(i);
+                } else {
+                    packageOps.filter(opNames, fractionToRemove);
+                }
+            }
+        }
+
+        private boolean isEmpty() {
+            final int packageCount = getPackageCount();
+            for (int i = packageCount - 1; i >= 0; i--) {
+                final HistoricalPackageOps packageOps = mHistoricalPackageOps.valueAt(i);
+                if (!packageOps.isEmpty()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private void increaseAccessCount(int opCode, @NonNull String packageName,
+                @UidState int uidState, @OpFlags int flags, long increment) {
+            getOrCreateHistoricalPackageOps(packageName).increaseAccessCount(
+                    opCode, uidState, flags, increment);
+        }
+
+        private void increaseRejectCount(int opCode, @NonNull String packageName,
+                @UidState int uidState,  @OpFlags int flags, long increment) {
+            getOrCreateHistoricalPackageOps(packageName).increaseRejectCount(
+                    opCode, uidState, flags, increment);
+        }
+
+        private void increaseAccessDuration(int opCode, @NonNull String packageName,
+                @UidState int uidState, @OpFlags int flags, long increment) {
+            getOrCreateHistoricalPackageOps(packageName).increaseAccessDuration(
+                    opCode, uidState, flags, increment);
+        }
+
+        /**
+         * @return The UID for which the data is related.
+         */
+        public int getUid() {
+            return mUid;
+        }
+
+        /**
+         * Gets number of packages with historical ops.
+         *
+         * @return The number of packages with historical ops.
+         *
+         * @see #getPackageOpsAt(int)
+         */
+        public @IntRange(from = 0) int getPackageCount() {
+            if (mHistoricalPackageOps == null) {
+                return 0;
+            }
+            return mHistoricalPackageOps.size();
+        }
+
+        /**
+         * Gets the historical package ops at a given index.
+         *
+         * @param index The index.
+         *
+         * @return The historical package ops at the given index.
+         *
+         * @see #getPackageCount()
+         */
+        public @NonNull HistoricalPackageOps getPackageOpsAt(@IntRange(from = 0) int index) {
+            if (mHistoricalPackageOps == null) {
+                throw new IndexOutOfBoundsException();
+            }
+            return mHistoricalPackageOps.valueAt(index);
+        }
+
+        /**
+         * Gets the historical package ops for a given package.
+         *
+         * @param packageName The package.
+         *
+         * @return The historical ops for the package.
+         */
+        public @Nullable HistoricalPackageOps getPackageOps(@NonNull String packageName) {
+            if (mHistoricalPackageOps == null) {
+                return null;
+            }
+            return mHistoricalPackageOps.get(packageName);
+        }
+
+        private void clearHistory(@NonNull String packageName) {
+            if (mHistoricalPackageOps != null) {
+                mHistoricalPackageOps.remove(packageName);
+            }
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeInt(mUid);
+            parcel.writeTypedArrayMap(mHistoricalPackageOps, flags);
+        }
+
+        private void accept(@NonNull HistoricalOpsVisitor visitor) {
+            visitor.visitHistoricalUidOps(this);
+            final int packageCount = getPackageCount();
+            for (int i = 0; i < packageCount; i++) {
+                getPackageOpsAt(i).accept(visitor);
+            }
+        }
+
+        private @NonNull HistoricalPackageOps getOrCreateHistoricalPackageOps(
+                @NonNull String packageName) {
+            if (mHistoricalPackageOps == null) {
+                mHistoricalPackageOps = new ArrayMap<>();
+            }
+            HistoricalPackageOps historicalPackageOp = mHistoricalPackageOps.get(packageName);
+            if (historicalPackageOp == null) {
+                historicalPackageOp = new HistoricalPackageOps(packageName);
+                mHistoricalPackageOps.put(packageName, historicalPackageOp);
+            }
+            return historicalPackageOp;
+        }
+
+
+        public static final @android.annotation.NonNull Creator<HistoricalUidOps> CREATOR = new Creator<HistoricalUidOps>() {
+            @Override
+            public @NonNull HistoricalUidOps createFromParcel(@NonNull Parcel parcel) {
+                return new HistoricalUidOps(parcel);
+            }
+
+            @Override
+            public @NonNull HistoricalUidOps[] newArray(int size) {
+                return new HistoricalUidOps[size];
+            }
+        };
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final HistoricalUidOps other = (HistoricalUidOps) obj;
+            if (mUid != other.mUid) {
+                return false;
+            }
+            if (mHistoricalPackageOps == null) {
+                if (other.mHistoricalPackageOps != null) {
+                    return false;
+                }
+            } else if (!mHistoricalPackageOps.equals(other.mHistoricalPackageOps)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mUid;
+            result = 31 * result + (mHistoricalPackageOps != null
+                    ? mHistoricalPackageOps.hashCode() : 0);
+            return result;
+        }
+    }
+
+    /**
+     * This class represents historical app op information about a package.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final class HistoricalPackageOps implements Parcelable {
+        private final @NonNull String mPackageName;
+        private @Nullable ArrayMap<String, HistoricalOp> mHistoricalOps;
+
+        /** @hide */
+        public HistoricalPackageOps(@NonNull String packageName) {
+            mPackageName = packageName;
+        }
+
+        private HistoricalPackageOps(@NonNull HistoricalPackageOps other) {
+            mPackageName = other.mPackageName;
+            final int opCount = other.getOpCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalOp origOp = other.getOpAt(i);
+                final HistoricalOp cloneOp = new HistoricalOp(origOp);
+                if (mHistoricalOps == null) {
+                    mHistoricalOps = new ArrayMap<>(opCount);
+                }
+                mHistoricalOps.put(cloneOp.getOpName(), cloneOp);
+            }
+        }
+
+        private HistoricalPackageOps(@NonNull Parcel parcel) {
+            mPackageName = parcel.readString();
+            mHistoricalOps = parcel.createTypedArrayMap(HistoricalOp.CREATOR);
+        }
+
+        private @Nullable HistoricalPackageOps splice(double fractionToRemove) {
+            HistoricalPackageOps splice = null;
+            final int opCount = getOpCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalOp origOps = getOpAt(i);
+                final HistoricalOp spliceOps = origOps.splice(fractionToRemove);
+                if (spliceOps != null) {
+                    if (splice == null) {
+                        splice = new HistoricalPackageOps(mPackageName);
+                    }
+                    if (splice.mHistoricalOps == null) {
+                        splice.mHistoricalOps = new ArrayMap<>();
+                    }
+                    splice.mHistoricalOps.put(spliceOps.getOpName(), spliceOps);
+                }
+            }
+            return splice;
+        }
+
+        private void merge(@NonNull HistoricalPackageOps other) {
+            final int opCount = other.getOpCount();
+            for (int i = 0; i < opCount; i++) {
+                final HistoricalOp otherOp = other.getOpAt(i);
+                final HistoricalOp thisOp = getOp(otherOp.getOpName());
+                if (thisOp != null) {
+                    thisOp.merge(otherOp);
+                } else {
+                    if (mHistoricalOps == null) {
+                        mHistoricalOps = new ArrayMap<>();
+                    }
+                    mHistoricalOps.put(otherOp.getOpName(), otherOp);
+                }
+            }
+        }
+
+        private void filter(@Nullable String[] opNames, double scaleFactor) {
+            final int opCount = getOpCount();
+            for (int i = opCount - 1; i >= 0; i--) {
+                final HistoricalOp op = mHistoricalOps.valueAt(i);
+                if (opNames != null && !ArrayUtils.contains(opNames, op.getOpName())) {
+                    mHistoricalOps.removeAt(i);
+                } else {
+                    op.filter(scaleFactor);
+                }
+            }
+        }
+
+        private boolean isEmpty() {
+            final int opCount = getOpCount();
+            for (int i = opCount - 1; i >= 0; i--) {
+                final HistoricalOp op = mHistoricalOps.valueAt(i);
+                if (!op.isEmpty()) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        private void increaseAccessCount(int opCode, @UidState int uidState,
+                @OpFlags int flags, long increment) {
+            getOrCreateHistoricalOp(opCode).increaseAccessCount(uidState, flags, increment);
+        }
+
+        private void increaseRejectCount(int opCode, @UidState int uidState,
+                @OpFlags int flags, long increment) {
+            getOrCreateHistoricalOp(opCode).increaseRejectCount(uidState, flags, increment);
+        }
+
+        private void increaseAccessDuration(int opCode, @UidState int uidState,
+                @OpFlags int flags, long increment) {
+            getOrCreateHistoricalOp(opCode).increaseAccessDuration(uidState, flags, increment);
+        }
+
+        /**
+         * Gets the package name which the data represents.
+         *
+         * @return The package name which the data represents.
+         */
+        public @NonNull String getPackageName() {
+            return mPackageName;
+        }
+
+        /**
+         * Gets number historical app ops.
+         *
+         * @return The number historical app ops.
+         * @see #getOpAt(int)
+         */
+        public @IntRange(from = 0) int getOpCount() {
+            if (mHistoricalOps == null) {
+                return 0;
+            }
+            return mHistoricalOps.size();
+        }
+
+        /**
+         * Gets the historical op at a given index.
+         *
+         * @param index The index to lookup.
+         * @return The op at the given index.
+         * @see #getOpCount()
+         */
+        public @NonNull HistoricalOp getOpAt(@IntRange(from = 0) int index) {
+            if (mHistoricalOps == null) {
+                throw new IndexOutOfBoundsException();
+            }
+            return mHistoricalOps.valueAt(index);
+        }
+
+        /**
+         * Gets the historical entry for a given op name.
+         *
+         * @param opName The op name.
+         * @return The historical entry for that op name.
+         */
+        public @Nullable HistoricalOp getOp(@NonNull String opName) {
+            if (mHistoricalOps == null) {
+                return null;
+            }
+            return mHistoricalOps.get(opName);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel parcel, int flags) {
+            parcel.writeString(mPackageName);
+            parcel.writeTypedArrayMap(mHistoricalOps, flags);
+        }
+
+        private void accept(@NonNull HistoricalOpsVisitor visitor) {
+            visitor.visitHistoricalPackageOps(this);
+            final int opCount = getOpCount();
+            for (int i = 0; i < opCount; i++) {
+                getOpAt(i).accept(visitor);
+            }
+        }
+
+        private @NonNull HistoricalOp getOrCreateHistoricalOp(int opCode) {
+            if (mHistoricalOps == null) {
+                mHistoricalOps = new ArrayMap<>();
+            }
+            final String opStr = sOpToString[opCode];
+            HistoricalOp op = mHistoricalOps.get(opStr);
+            if (op == null) {
+                op = new HistoricalOp(opCode);
+                mHistoricalOps.put(opStr, op);
+            }
+            return op;
+        }
+
+        public static final @android.annotation.NonNull Creator<HistoricalPackageOps> CREATOR =
+                new Creator<HistoricalPackageOps>() {
+            @Override
+            public @NonNull HistoricalPackageOps createFromParcel(@NonNull Parcel parcel) {
+                return new HistoricalPackageOps(parcel);
+            }
+
+            @Override
+            public @NonNull HistoricalPackageOps[] newArray(int size) {
+                return new HistoricalPackageOps[size];
+            }
+        };
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final HistoricalPackageOps other = (HistoricalPackageOps) obj;
+            if (!mPackageName.equals(other.mPackageName)) {
+                return false;
+            }
+            if (mHistoricalOps == null) {
+                if (other.mHistoricalOps != null) {
+                    return false;
+                }
+            } else if (!mHistoricalOps.equals(other.mHistoricalOps)) {
+                return false;
+            }
+            return true;
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mPackageName != null ? mPackageName.hashCode() : 0;
+            result = 31 * result + (mHistoricalOps != null ? mHistoricalOps.hashCode() : 0);
+            return result;
+        }
+    }
+
+    /**
+     * This class represents historical information about an app op.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    public static final class HistoricalOp implements Parcelable {
+        private final int mOp;
+        private @Nullable LongSparseLongArray mAccessCount;
+        private @Nullable LongSparseLongArray mRejectCount;
+        private @Nullable LongSparseLongArray mAccessDuration;
+
+        /** @hide */
+        public HistoricalOp(int op) {
+            mOp = op;
+        }
+
+        private HistoricalOp(@NonNull HistoricalOp other) {
+            mOp = other.mOp;
+            if (other.mAccessCount != null) {
+                mAccessCount = other.mAccessCount.clone();
+            }
+            if (other.mRejectCount != null) {
+                mRejectCount = other.mRejectCount.clone();
+            }
+            if (other.mAccessDuration != null) {
+                mAccessDuration = other.mAccessDuration.clone();
+            }
+        }
+
+        private HistoricalOp(@NonNull Parcel parcel) {
+            mOp = parcel.readInt();
+            mAccessCount = readLongSparseLongArrayFromParcel(parcel);
+            mRejectCount = readLongSparseLongArrayFromParcel(parcel);
+            mAccessDuration = readLongSparseLongArrayFromParcel(parcel);
+        }
+
+        private void filter(double scaleFactor) {
+            scale(mAccessCount, scaleFactor);
+            scale(mRejectCount, scaleFactor);
+            scale(mAccessDuration, scaleFactor);
+        }
+
+        private boolean isEmpty() {
+            return !hasData(mAccessCount)
+                    && !hasData(mRejectCount)
+                    && !hasData(mAccessDuration);
+        }
+
+        private boolean hasData(@NonNull LongSparseLongArray array) {
+            return (array != null && array.size() > 0);
+        }
+
+        private @Nullable HistoricalOp splice(double fractionToRemove) {
+            final HistoricalOp splice = new HistoricalOp(mOp);
+            splice(mAccessCount, splice::getOrCreateAccessCount, fractionToRemove);
+            splice(mRejectCount, splice::getOrCreateRejectCount, fractionToRemove);
+            splice(mAccessDuration, splice::getOrCreateAccessDuration, fractionToRemove);
+            return splice;
+        }
+
+        private static void splice(@Nullable LongSparseLongArray sourceContainer,
+                @NonNull Supplier<LongSparseLongArray> destContainerProvider,
+                    double fractionToRemove) {
+            if (sourceContainer != null) {
+                final int size = sourceContainer.size();
+                for (int i = 0; i < size; i++) {
+                    final long key = sourceContainer.keyAt(i);
+                    final long value = sourceContainer.valueAt(i);
+                    final long removedFraction = Math.round(value * fractionToRemove);
+                    if (removedFraction > 0) {
+                        destContainerProvider.get().put(key, removedFraction);
+                        sourceContainer.put(key, value - removedFraction);
+                    }
+                }
+            }
+        }
+
+        private void merge(@NonNull HistoricalOp other) {
+            merge(this::getOrCreateAccessCount, other.mAccessCount);
+            merge(this::getOrCreateRejectCount, other.mRejectCount);
+            merge(this::getOrCreateAccessDuration, other.mAccessDuration);
+        }
+
+        private void increaseAccessCount(@UidState int uidState, @OpFlags int flags,
+                long increment) {
+            increaseCount(getOrCreateAccessCount(), uidState, flags, increment);
+        }
+
+        private void increaseRejectCount(@UidState int uidState, @OpFlags int flags,
+                long increment) {
+            increaseCount(getOrCreateRejectCount(), uidState, flags, increment);
+        }
+
+        private void increaseAccessDuration(@UidState int uidState, @OpFlags int flags,
+                long increment) {
+            increaseCount(getOrCreateAccessDuration(), uidState, flags, increment);
+        }
+
+        private void increaseCount(@NonNull LongSparseLongArray counts,
+                @UidState int uidState, @OpFlags int flags, long increment) {
+            while (flags != 0) {
+                final int flag = 1 << Integer.numberOfTrailingZeros(flags);
+                flags &= ~flag;
+                final long key = makeKey(uidState, flag);
+                counts.put(key, counts.get(key) + increment);
+            }
+        }
+
+        /**
+         * Gets the op name.
+         *
+         * @return The op name.
+         */
+        public @NonNull String getOpName() {
+            return sOpToString[mOp];
+        }
+
+        /** @hide */
+        public int getOpCode() {
+            return mOp;
+        }
+
+        /**
+         * Gets the number times the op was accessed (performed) in the foreground.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return The times the op was accessed in the foreground.
+         *
+         * @see #getBackgroundAccessCount(int)
+         * @see #getAccessCount(int, int, int)
+         */
+        public long getForegroundAccessCount(@OpFlags int flags) {
+            return sumForFlagsInStates(mAccessCount, MAX_PRIORITY_UID_STATE,
+                    resolveFirstUnrestrictedUidState(mOp), flags);
+        }
+
+        /**
+         * Gets the number times the op was accessed (performed) in the background.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return The times the op was accessed in the background.
+         *
+         * @see #getForegroundAccessCount(int)
+         * @see #getAccessCount(int, int, int)
+         */
+        public long getBackgroundAccessCount(@OpFlags int flags) {
+            return sumForFlagsInStates(mAccessCount, resolveLastRestrictedUidState(mOp),
+                    MIN_PRIORITY_UID_STATE, flags);
+        }
+
+        /**
+         * Gets the number times the op was accessed (performed) for a
+         * range of uid states.
+         *
+         * @param fromUidState The UID state from which to query. Could be one of
+         * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
+         * {@link #UID_STATE_FOREGROUND_SERVICE_LOCATION},
+         * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
+         * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
+         * @param toUidState The UID state to which to query.
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         *
+         * @return The times the op was accessed for the given UID state.
+         *
+         * @see #getForegroundAccessCount(int)
+         * @see #getBackgroundAccessCount(int)
+         */
+        public long getAccessCount(@UidState int fromUidState, @UidState int toUidState,
+                @OpFlags int flags) {
+            return sumForFlagsInStates(mAccessCount, fromUidState, toUidState, flags);
+        }
+
+        /**
+         * Gets the number times the op was rejected in the foreground.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return The times the op was rejected in the foreground.
+         *
+         * @see #getBackgroundRejectCount(int)
+         * @see #getRejectCount(int, int, int)
+         */
+        public long getForegroundRejectCount(@OpFlags int flags) {
+            return sumForFlagsInStates(mRejectCount, MAX_PRIORITY_UID_STATE,
+                    resolveFirstUnrestrictedUidState(mOp), flags);
+        }
+
+        /**
+         * Gets the number times the op was rejected in the background.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return The times the op was rejected in the background.
+         *
+         * @see #getForegroundRejectCount(int)
+         * @see #getRejectCount(int, int, int)
+         */
+        public long getBackgroundRejectCount(@OpFlags int flags) {
+            return sumForFlagsInStates(mRejectCount, resolveLastRestrictedUidState(mOp),
+                    MIN_PRIORITY_UID_STATE, flags);
+        }
+
+        /**
+         * Gets the number times the op was rejected for a given range of UID states.
+         *
+         * @param fromUidState The UID state from which to query. Could be one of
+         * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
+         * {@link #UID_STATE_FOREGROUND_SERVICE_LOCATION},
+         * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
+         * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
+         * @param toUidState The UID state to which to query.
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         *
+         * @return The times the op was rejected for the given UID state.
+         *
+         * @see #getForegroundRejectCount(int)
+         * @see #getBackgroundRejectCount(int)
+         */
+        public long getRejectCount(@UidState int fromUidState, @UidState int toUidState,
+                @OpFlags int flags) {
+            return sumForFlagsInStates(mRejectCount, fromUidState, toUidState, flags);
+        }
+
+        /**
+         * Gets the total duration the app op was accessed (performed) in the foreground.
+         * The duration is in wall time.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return The total duration the app op was accessed in the foreground.
+         *
+         * @see #getBackgroundAccessDuration(int)
+         * @see #getAccessDuration(int, int, int)
+         */
+        public long getForegroundAccessDuration(@OpFlags int flags) {
+            return sumForFlagsInStates(mAccessDuration, MAX_PRIORITY_UID_STATE,
+                    resolveFirstUnrestrictedUidState(mOp), flags);
+        }
+
+        /**
+         * Gets the total duration the app op was accessed (performed) in the background.
+         * The duration is in wall time.
+         *
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         * @return The total duration the app op was accessed in the background.
+         *
+         * @see #getForegroundAccessDuration(int)
+         * @see #getAccessDuration(int, int, int)
+         */
+        public long getBackgroundAccessDuration(@OpFlags int flags) {
+            return sumForFlagsInStates(mAccessDuration, resolveLastRestrictedUidState(mOp),
+                    MIN_PRIORITY_UID_STATE, flags);
+        }
+
+        /**
+         * Gets the total duration the app op was accessed (performed) for a given
+         * range of UID states. The duration is in wall time.
+         *
+         * @param fromUidState The UID state from which to query. Could be one of
+         * {@link #UID_STATE_PERSISTENT}, {@link #UID_STATE_TOP},
+         * {@link #UID_STATE_FOREGROUND_SERVICE_LOCATION},
+         * {@link #UID_STATE_FOREGROUND_SERVICE}, {@link #UID_STATE_FOREGROUND},
+         * {@link #UID_STATE_BACKGROUND}, {@link #UID_STATE_CACHED}.
+         * @param toUidState The UID state from which to query.
+         * @param flags The flags which are any combination of
+         * {@link #OP_FLAG_SELF}, {@link #OP_FLAG_TRUSTED_PROXY},
+         * {@link #OP_FLAG_UNTRUSTED_PROXY}, {@link #OP_FLAG_TRUSTED_PROXIED},
+         * {@link #OP_FLAG_UNTRUSTED_PROXIED}. You can use {@link #OP_FLAGS_ALL}
+         * for any flag.
+         *
+         * @return The total duration the app op was accessed for the given UID state.
+         *
+         * @see #getForegroundAccessDuration(int)
+         * @see #getBackgroundAccessDuration(int)
+         */
+        public long getAccessDuration(@UidState int fromUidState, @UidState int toUidState,
+                @OpFlags int flags) {
+            return sumForFlagsInStates(mAccessDuration, fromUidState, toUidState, flags);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel parcel, int flags) {
+            parcel.writeInt(mOp);
+            writeLongSparseLongArrayToParcel(mAccessCount, parcel);
+            writeLongSparseLongArrayToParcel(mRejectCount, parcel);
+            writeLongSparseLongArrayToParcel(mAccessDuration, parcel);
+        }
+
+        @Override
+        public boolean equals(Object obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (obj == null || getClass() != obj.getClass()) {
+                return false;
+            }
+            final HistoricalOp other = (HistoricalOp) obj;
+            if (mOp != other.mOp) {
+                return false;
+            }
+            if (!Objects.equals(mAccessCount, other.mAccessCount)) {
+                return false;
+            }
+            if (!Objects.equals(mRejectCount, other.mRejectCount)) {
+                return false;
+            }
+            return Objects.equals(mAccessDuration, other.mAccessDuration);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mOp;
+            result = 31 * result + Objects.hashCode(mAccessCount);
+            result = 31 * result + Objects.hashCode(mRejectCount);
+            result = 31 * result + Objects.hashCode(mAccessDuration);
+            return result;
+        }
+
+        private void accept(@NonNull HistoricalOpsVisitor visitor) {
+            visitor.visitHistoricalOp(this);
+        }
+
+        private @NonNull LongSparseLongArray getOrCreateAccessCount() {
+            if (mAccessCount == null) {
+                mAccessCount = new LongSparseLongArray();
+            }
+            return mAccessCount;
+        }
+
+        private @NonNull LongSparseLongArray getOrCreateRejectCount() {
+            if (mRejectCount == null) {
+                mRejectCount = new LongSparseLongArray();
+            }
+            return mRejectCount;
+        }
+
+        private @NonNull LongSparseLongArray getOrCreateAccessDuration() {
+            if (mAccessDuration == null) {
+                mAccessDuration = new LongSparseLongArray();
+            }
+            return mAccessDuration;
+        }
+
+        /**
+         * Multiplies the entries in the array with the passed in scale factor and
+         * rounds the result at up 0.5 boundary.
+         *
+         * @param data The data to scale.
+         * @param scaleFactor The scale factor.
+         */
+        private static void scale(@NonNull LongSparseLongArray data, double scaleFactor) {
+            if (data != null) {
+                final int size = data.size();
+                for (int i = 0; i < size; i++) {
+                    data.put(data.keyAt(i), (long) HistoricalOps.round(
+                            (double) data.valueAt(i) * scaleFactor));
+                }
+            }
+        }
+
+        /**
+         * Merges two arrays while lazily acquiring the destination.
+         *
+         * @param thisSupplier The destination supplier.
+         * @param other The array to merge in.
+         */
+        private static void merge(@NonNull Supplier<LongSparseLongArray> thisSupplier,
+                @Nullable LongSparseLongArray other) {
+            if (other != null) {
+                final int otherSize = other.size();
+                for (int i = 0; i < otherSize; i++) {
+                    final LongSparseLongArray that = thisSupplier.get();
+                    final long otherKey = other.keyAt(i);
+                    final long otherValue = other.valueAt(i);
+                    that.put(otherKey, that.get(otherKey) + otherValue);
+                }
+            }
+        }
+
+        /** @hide */
+        public @Nullable LongSparseArray<Object> collectKeys() {
+            LongSparseArray<Object> result = AppOpsManager.collectKeys(mAccessCount,
+                null /*result*/);
+            result = AppOpsManager.collectKeys(mRejectCount, result);
+            result = AppOpsManager.collectKeys(mAccessDuration, result);
+            return result;
+        }
+
+        public static final @android.annotation.NonNull Creator<HistoricalOp> CREATOR =
+                new Creator<HistoricalOp>() {
+            @Override
+            public @NonNull HistoricalOp createFromParcel(@NonNull Parcel source) {
+                return new HistoricalOp(source);
+            }
+
+            @Override
+            public @NonNull HistoricalOp[] newArray(int size) {
+                return new HistoricalOp[size];
+            }
+        };
+    }
+
+    /**
+     * Computes the sum of the counts for the given flags in between the begin and
+     * end UID states.
+     *
+     * @param counts The data array.
+     * @param beginUidState The beginning UID state (exclusive).
+     * @param endUidState The end UID state.
+     * @param flags The UID flags.
+     * @return The sum.
+     */
+    private static long sumForFlagsInStates(@Nullable LongSparseLongArray counts,
+            @UidState int beginUidState, @UidState int endUidState, @OpFlags int flags) {
+        if (counts == null) {
+            return 0;
+        }
+        long sum = 0;
+        while (flags != 0) {
+            final int flag = 1 << Integer.numberOfTrailingZeros(flags);
+            flags &= ~flag;
+            for (int uidState : UID_STATES) {
+                if (uidState < beginUidState || uidState > endUidState) {
+                    continue;
+                }
+                final long key = makeKey(uidState, flag);
+                sum += counts.get(key);
+            }
+        }
+        return sum;
+    }
+
+    /**
+     * Finds the first non-negative value for the given flags in between the begin and
+     * end UID states.
+     *
+     * @param counts The data array.
+     * @param flags The UID flags.
+     * @param beginUidState The beginning UID state (exclusive).
+     * @param endUidState The end UID state.
+     * @return The non-negative value or -1.
+     */
+    private static long findFirstNonNegativeForFlagsInStates(@Nullable LongSparseLongArray counts,
+            @OpFlags int flags, @UidState int beginUidState, @UidState int endUidState) {
+        if (counts == null) {
+            return -1;
+        }
+        while (flags != 0) {
+            final int flag = 1 << Integer.numberOfTrailingZeros(flags);
+            flags &= ~flag;
+            for (int uidState : UID_STATES) {
+                if (uidState < beginUidState || uidState > endUidState) {
+                    continue;
+                }
+                final long key = makeKey(uidState, flag);
+                final long value = counts.get(key);
+                if (value >= 0) {
+                    return value;
+                }
+            }
+        }
+        return -1;
+    }
+
+    /**
+     * Finds the first non-null value for the given flags in between the begin and
+     * end UID states.
+     *
+     * @param counts The data array.
+     * @param flags The UID flags.
+     * @param beginUidState The beginning UID state (exclusive).
+     * @param endUidState The end UID state.
+     * @return The non-negative value or -1.
+     */
+    private static @Nullable String findFirstNonNullForFlagsInStates(
+            @Nullable LongSparseArray<String> counts, @OpFlags int flags,
+            @UidState int beginUidState, @UidState int endUidState) {
+        if (counts == null) {
+            return null;
+        }
+        while (flags != 0) {
+            final int flag = 1 << Integer.numberOfTrailingZeros(flags);
+            flags &= ~flag;
+            for (int uidState : UID_STATES) {
+                if (uidState < beginUidState || uidState > endUidState) {
+                    continue;
+                }
+                final long key = makeKey(uidState, flag);
+                final String value = counts.get(key);
+                if (value != null) {
+                    return value;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Callback for notification of changes to operation state.
+     */
+    public interface OnOpChangedListener {
+        public void onOpChanged(String op, String packageName);
+    }
+
+    /**
+     * Callback for notification of changes to operation active state.
+     *
+     * @hide
+     */
+    @TestApi
+    public interface OnOpActiveChangedListener {
+        /**
+         * Called when the active state of an app op changes.
+         *
+         * @param code The op code.
+         * @param uid The UID performing the operation.
+         * @param packageName The package performing the operation.
+         * @param active Whether the operation became active or inactive.
+         */
+        void onOpActiveChanged(int code, int uid, String packageName, boolean active);
+    }
+
+    /**
+     * Callback for notification of an op being noted.
+     *
+     * @hide
+     */
+    public interface OnOpNotedListener {
+        /**
+         * Called when an op was noted.
+         *
+         * @param code The op code.
+         * @param uid The UID performing the operation.
+         * @param packageName The package performing the operation.
+         * @param result The result of the note.
+         */
+        void onOpNoted(int code, int uid, String packageName, int result);
+    }
+
+    /**
+     * Callback for notification of changes to operation state.
+     * This allows you to see the raw op codes instead of strings.
+     * @hide
+     */
+    public static class OnOpChangedInternalListener implements OnOpChangedListener {
+        public void onOpChanged(String op, String packageName) { }
+        public void onOpChanged(int op, String packageName) { }
+    }
+
+    AppOpsManager(Context context, IAppOpsService service) {
+        mContext = context;
+        mService = service;
+    }
+
+    /**
+     * Retrieve current operation state for all applications.
+     *
+     * The mode of the ops returned are set for the package but may not reflect their effective
+     * state due to UID policy or because it's controlled by a different master op.
+     *
+     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
+     * if the effective mode is needed.
+     *
+     * @param ops The set of operations you are interested in, or null if you want all of them.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+    public @NonNull List<AppOpsManager.PackageOps> getPackagesForOps(@Nullable String[] ops) {
+        final int opCount = ops.length;
+        final int[] opCodes = new int[opCount];
+        for (int i = 0; i < opCount; i++) {
+            opCodes[i] = sOpStrToOp.get(ops[i]);
+        }
+        final List<AppOpsManager.PackageOps> result = getPackagesForOps(opCodes);
+        return (result != null) ? result : Collections.emptyList();
+    }
+
+    /**
+     * Retrieve current operation state for all applications.
+     *
+     * The mode of the ops returned are set for the package but may not reflect their effective
+     * state due to UID policy or because it's controlled by a different master op.
+     *
+     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
+     * if the effective mode is needed.
+     *
+     * @param ops The set of operations you are interested in, or null if you want all of them.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+    @UnsupportedAppUsage
+    public List<AppOpsManager.PackageOps> getPackagesForOps(int[] ops) {
+        try {
+            return mService.getPackagesForOps(ops);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieve current operation state for one application.
+     *
+     * The mode of the ops returned are set for the package but may not reflect their effective
+     * state due to UID policy or because it's controlled by a different master op.
+     *
+     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
+     * if the effective mode is needed.
+     *
+     * @param uid The uid of the application of interest.
+     * @param packageName The name of the application of interest.
+     * @param ops The set of operations you are interested in, or null if you want all of them.
+     *
+     * @deprecated The int op codes are not stable and you should use the string based op
+     * names which are stable and namespaced. Use
+     * {@link #getOpsForPackage(int, String, String...)})}.
+     *
+     * @hide
+     * @removed
+     */
+    @Deprecated
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+    public @NonNull List<PackageOps> getOpsForPackage(int uid, @NonNull String packageName,
+            @Nullable int[] ops) {
+        try {
+            return mService.getOpsForPackage(uid, packageName, ops);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieve current operation state for one application. The UID and the
+     * package must match.
+     *
+     * The mode of the ops returned are set for the package but may not reflect their effective
+     * state due to UID policy or because it's controlled by a different master op.
+     *
+     * Use {@link #unsafeCheckOp(String, int, String)}} or {@link #noteOp(String, int, String)}
+     * if the effective mode is needed.
+     *
+     * @param uid The uid of the application of interest.
+     * @param packageName The name of the application of interest.
+     * @param ops The set of operations you are interested in, or null if you want all of them.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+    public @NonNull List<AppOpsManager.PackageOps> getOpsForPackage(int uid,
+            @NonNull String packageName, @Nullable String... ops) {
+        int[] opCodes = null;
+        if (ops != null) {
+            opCodes = new int[ops.length];
+            for (int i = 0; i < ops.length; i++) {
+                opCodes[i] = strOpToOp(ops[i]);
+            }
+        }
+        try {
+            final List<PackageOps> result = mService.getOpsForPackage(uid, packageName, opCodes);
+            if (result == null) {
+                return Collections.emptyList();
+            }
+            return result;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieve historical app op stats for a period.
+     *
+     * @param request A request object describing the data being queried for.
+     * @param executor Executor on which to run the callback. If <code>null</code>
+     *     the callback is executed on the default executor running on the main thread.
+     * @param callback Callback on which to deliver the result.
+     *
+     * @throws IllegalArgumentException If any of the argument contracts is violated.
+     *
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.GET_APP_OPS_STATS)
+    public void getHistoricalOps(@NonNull HistoricalOpsRequest request,
+            @NonNull Executor executor, @NonNull Consumer<HistoricalOps> callback) {
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        try {
+            mService.getHistoricalOps(request.mUid, request.mPackageName, request.mOpNames,
+                    request.mBeginTimeMillis, request.mEndTimeMillis, request.mFlags,
+                    new RemoteCallback((result) -> {
+                final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> callback.accept(ops));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Retrieve historical app op stats for a period.
+     *  <p>
+     *  This method queries only the on disk state and the returned ops are raw,
+     *  which is their times are relative to the history start as opposed to the
+     *  epoch start.
+     *
+     * @param request A request object describing the data being queried for.
+     * @param executor Executor on which to run the callback. If <code>null</code>
+     *     the callback is executed on the default executor running on the main thread.
+     * @param callback Callback on which to deliver the result.
+     *
+     * @throws IllegalArgumentException If any of the argument contracts is violated.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void getHistoricalOpsFromDiskRaw(@NonNull HistoricalOpsRequest request,
+            @Nullable Executor executor, @NonNull Consumer<HistoricalOps> callback) {
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        try {
+            mService.getHistoricalOpsFromDiskRaw(request.mUid, request.mPackageName,
+                    request.mOpNames, request.mBeginTimeMillis, request.mEndTimeMillis,
+                    request.mFlags, new RemoteCallback((result) -> {
+                final HistoricalOps ops = result.getParcelable(KEY_HISTORICAL_OPS);
+                final long identity = Binder.clearCallingIdentity();
+                try {
+                    executor.execute(() -> callback.accept(ops));
+                } finally {
+                    Binder.restoreCallingIdentity(identity);
+                }
+            }));
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Reloads the non historical state to allow testing the read/write path.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void reloadNonHistoricalState() {
+        try {
+            mService.reloadNonHistoricalState();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets given app op in the specified mode for app ops in the UID.
+     * This applies to all apps currently in the UID or installed in
+     * this UID in the future.
+     *
+     * @param code The app op.
+     * @param uid The UID for which to set the app.
+     * @param mode The app op mode to set.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
+    public void setUidMode(int code, int uid, @Mode int mode) {
+        try {
+            mService.setUidMode(code, uid, mode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets given app op in the specified mode for app ops in the UID.
+     * This applies to all apps currently in the UID or installed in
+     * this UID in the future.
+     *
+     * @param appOp The app op.
+     * @param uid The UID for which to set the app.
+     * @param mode The app op mode to set.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
+    public void setUidMode(String appOp, int uid, @Mode int mode) {
+        try {
+            mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void setUserRestriction(int code, boolean restricted, IBinder token) {
+        setUserRestriction(code, restricted, token, /*exceptionPackages*/null);
+    }
+
+    /** @hide */
+    public void setUserRestriction(int code, boolean restricted, IBinder token,
+            String[] exceptionPackages) {
+        setUserRestrictionForUser(code, restricted, token, exceptionPackages, mContext.getUserId());
+    }
+
+    /** @hide */
+    public void setUserRestrictionForUser(int code, boolean restricted, IBinder token,
+            String[] exceptionPackages, int userId) {
+        try {
+            mService.setUserRestriction(code, restricted, token, userId, exceptionPackages);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @TestApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
+    public void setMode(int code, int uid, String packageName, @Mode int mode) {
+        try {
+            mService.setMode(code, uid, packageName, mode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Change the operating mode for the given op in the given app package.  You must pass
+     * in both the uid and name of the application whose mode is being modified; if these
+     * do not match, the modification will not be applied.
+     *
+     * @param op The operation to modify.  One of the OPSTR_* constants.
+     * @param uid The user id of the application whose mode will be changed.
+     * @param packageName The name of the application package name whose mode will
+     * be changed.
+     * @hide
+     */
+    @TestApi
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
+    public void setMode(String op, int uid, String packageName, @Mode int mode) {
+        try {
+            mService.setMode(strOpToOp(op), uid, packageName, mode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Set a non-persisted restriction on an audio operation at a stream-level.
+     * Restrictions are temporary additional constraints imposed on top of the persisted rules
+     * defined by {@link #setMode}.
+     *
+     * @param code The operation to restrict.
+     * @param usage The {@link android.media.AudioAttributes} usage value.
+     * @param mode The restriction mode (MODE_IGNORED,MODE_ERRORED) or MODE_ALLOWED to unrestrict.
+     * @param exceptionPackages Optional list of packages to exclude from the restriction.
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
+    @UnsupportedAppUsage
+    public void setRestriction(int code, @AttributeUsage int usage, @Mode int mode,
+            String[] exceptionPackages) {
+        try {
+            final int uid = Binder.getCallingUid();
+            mService.setAudioRestriction(code, usage, uid, mode, exceptionPackages);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
+    @UnsupportedAppUsage
+    public void resetAllModes() {
+        try {
+            mService.resetAllModes(mContext.getUserId(), null);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets the app op name associated with a given permission.
+     * The app op name is one of the public constants defined
+     * in this class such as {@link #OPSTR_COARSE_LOCATION}.
+     * This API is intended to be used for mapping runtime
+     * permissions to the corresponding app op.
+     *
+     * @param permission The permission.
+     * @return The app op associated with the permission or null.
+     */
+    public static String permissionToOp(String permission) {
+        final Integer opCode = sPermToOp.get(permission);
+        if (opCode == null) {
+            return null;
+        }
+        return sOpToString[opCode];
+    }
+
+    /**
+     * Monitor for changes to the operating mode for the given op in the given app package.
+     * You can watch op changes only for your UID.
+     *
+     * @param op The operation to monitor, one of OPSTR_*.
+     * @param packageName The name of the application to monitor.
+     * @param callback Where to report changes.
+     */
+    public void startWatchingMode(@NonNull String op, @Nullable String packageName,
+            @NonNull final OnOpChangedListener callback) {
+        startWatchingMode(strOpToOp(op), packageName, callback);
+    }
+
+    /**
+     * Monitor for changes to the operating mode for the given op in the given app package.
+     * You can watch op changes only for your UID.
+     *
+     * @param op The operation to monitor, one of OPSTR_*.
+     * @param packageName The name of the application to monitor.
+     * @param flags Option flags: any combination of {@link #WATCH_FOREGROUND_CHANGES} or 0.
+     * @param callback Where to report changes.
+     */
+    public void startWatchingMode(@NonNull String op, @Nullable String packageName, int flags,
+            @NonNull final OnOpChangedListener callback) {
+        startWatchingMode(strOpToOp(op), packageName, flags, callback);
+    }
+
+    /**
+     * Monitor for changes to the operating mode for the given op in the given app package.
+     *
+     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
+     * you can watch changes only for your UID.
+     *
+     * @param op The operation to monitor, one of OP_*.
+     * @param packageName The name of the application to monitor.
+     * @param callback Where to report changes.
+     * @hide
+     */
+    @RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true)
+    public void startWatchingMode(int op, String packageName, final OnOpChangedListener callback) {
+        startWatchingMode(op, packageName, 0, callback);
+    }
+
+    /**
+     * Monitor for changes to the operating mode for the given op in the given app package.
+     *
+     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
+     * you can watch changes only for your UID.
+     *
+     * @param op The operation to monitor, one of OP_*.
+     * @param packageName The name of the application to monitor.
+     * @param flags Option flags: any combination of {@link #WATCH_FOREGROUND_CHANGES} or 0.
+     * @param callback Where to report changes.
+     * @hide
+     */
+    @RequiresPermission(value=android.Manifest.permission.WATCH_APPOPS, conditional=true)
+    public void startWatchingMode(int op, String packageName, int flags,
+            final OnOpChangedListener callback) {
+        synchronized (mModeWatchers) {
+            IAppOpsCallback cb = mModeWatchers.get(callback);
+            if (cb == null) {
+                cb = new IAppOpsCallback.Stub() {
+                    public void opChanged(int op, int uid, String packageName) {
+                        if (callback instanceof OnOpChangedInternalListener) {
+                            ((OnOpChangedInternalListener)callback).onOpChanged(op, packageName);
+                        }
+                        if (sOpToString[op] != null) {
+                            callback.onOpChanged(sOpToString[op], packageName);
+                        }
+                    }
+                };
+                mModeWatchers.put(callback, cb);
+            }
+            try {
+                mService.startWatchingModeWithFlags(op, packageName, flags, cb);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    /**
+     * Stop monitoring that was previously started with {@link #startWatchingMode}.  All
+     * monitoring associated with this callback will be removed.
+     */
+    public void stopWatchingMode(@NonNull OnOpChangedListener callback) {
+        synchronized (mModeWatchers) {
+            IAppOpsCallback cb = mModeWatchers.remove(callback);
+            if (cb != null) {
+                try {
+                    mService.stopWatchingMode(cb);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
+     * Start watching for changes to the active state of app ops. An app op may be
+     * long running and it has a clear start and stop delimiters. If an op is being
+     * started or stopped by any package you will get a callback. To change the
+     * watched ops for a registered callback you need to unregister and register it
+     * again.
+     *
+     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
+     * you can watch changes only for your UID.
+     *
+     * @param ops The ops to watch.
+     * @param callback Where to report changes.
+     *
+     * @see #isOperationActive(int, int, String)
+     * @see #stopWatchingActive(OnOpActiveChangedListener)
+     * @see #startOp(int, int, String)
+     * @see #finishOp(int, int, String)
+     *
+     * @hide
+     */
+    @TestApi
+    // TODO: Uncomment below annotation once b/73559440 is fixed
+    // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
+    public void startWatchingActive(@NonNull int[] ops,
+            @NonNull OnOpActiveChangedListener callback) {
+        Preconditions.checkNotNull(ops, "ops cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        IAppOpsActiveCallback cb;
+        synchronized (mActiveWatchers) {
+            cb = mActiveWatchers.get(callback);
+            if (cb != null) {
+                return;
+            }
+            cb = new IAppOpsActiveCallback.Stub() {
+                @Override
+                public void opActiveChanged(int op, int uid, String packageName, boolean active) {
+                    callback.onOpActiveChanged(op, uid, packageName, active);
+                }
+            };
+            mActiveWatchers.put(callback, cb);
+        }
+        try {
+            mService.startWatchingActive(ops, cb);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Stop watching for changes to the active state of an app op. An app op may be
+     * long running and it has a clear start and stop delimiters. Unregistering a
+     * non-registered callback has no effect.
+     *
+     * @see #isOperationActive#(int, int, String)
+     * @see #startWatchingActive(int[], OnOpActiveChangedListener)
+     * @see #startOp(int, int, String)
+     * @see #finishOp(int, int, String)
+     *
+     * @hide
+     */
+    @TestApi
+    public void stopWatchingActive(@NonNull OnOpActiveChangedListener callback) {
+        synchronized (mActiveWatchers) {
+            final IAppOpsActiveCallback cb = mActiveWatchers.remove(callback);
+            if (cb != null) {
+                try {
+                    mService.stopWatchingActive(cb);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    /**
+     * Start watching for noted app ops. An app op may be immediate or long running.
+     * Immediate ops are noted while long running ones are started and stopped. This
+     * method allows registering a listener to be notified when an app op is noted. If
+     * an op is being noted by any package you will get a callback. To change the
+     * watched ops for a registered callback you need to unregister and register it again.
+     *
+     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
+     * you can watch changes only for your UID.
+     *
+     * @param ops The ops to watch.
+     * @param callback Where to report changes.
+     *
+     * @see #startWatchingActive(int[], OnOpActiveChangedListener)
+     * @see #stopWatchingNoted(OnOpNotedListener)
+     * @see #noteOp(String, int, String)
+     *
+     * @hide
+     */
+    @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
+    public void startWatchingNoted(@NonNull int[] ops, @NonNull OnOpNotedListener callback) {
+        IAppOpsNotedCallback cb;
+        synchronized (mNotedWatchers) {
+            cb = mNotedWatchers.get(callback);
+            if (cb != null) {
+                return;
+            }
+            cb = new IAppOpsNotedCallback.Stub() {
+                @Override
+                public void opNoted(int op, int uid, String packageName, int mode) {
+                    callback.onOpNoted(op, uid, packageName, mode);
+                }
+            };
+            mNotedWatchers.put(callback, cb);
+        }
+        try {
+            mService.startWatchingNoted(ops, cb);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Stop watching for noted app ops. An app op may be immediate or long running.
+     * Unregistering a non-registered callback has no effect.
+     *
+     * @see #startWatchingNoted(int[], OnOpNotedListener)
+     * @see #noteOp(String, int, String)
+     *
+     * @hide
+     */
+    public void stopWatchingNoted(@NonNull OnOpNotedListener callback) {
+        synchronized (mNotedWatchers) {
+            final IAppOpsNotedCallback cb = mNotedWatchers.get(callback);
+            if (cb != null) {
+                try {
+                    mService.stopWatchingNoted(cb);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    private String buildSecurityExceptionMsg(int op, int uid, String packageName) {
+        return packageName + " from uid " + uid + " not allowed to perform " + sOpNames[op];
+    }
+
+    /**
+     * {@hide}
+     */
+    @TestApi
+    public static int strOpToOp(@NonNull String op) {
+        Integer val = sOpStrToOp.get(op);
+        if (val == null) {
+            throw new IllegalArgumentException("Unknown operation string: " + op);
+        }
+        return val;
+    }
+
+    /**
+     * Do a quick check for whether an application might be able to perform an operation.
+     * This is <em>not</em> a security check; you must use {@link #noteOp(String, int, String)}
+     * or {@link #startOp(String, int, String)} for your actual security checks, which also
+     * ensure that the given uid and package name are consistent.  This function can just be
+     * used for a quick check to see if an operation has been disabled for the application,
+     * as an early reject of some work.  This does not modify the time stamp or other data
+     * about the operation.
+     *
+     * <p>Important things this will not do (which you need to ultimate use
+     * {@link #noteOp(String, int, String)} or {@link #startOp(String, int, String)} to cover):</p>
+     * <ul>
+     *     <li>Verifying the uid and package are consistent, so callers can't spoof
+     *     their identity.</li>
+     *     <li>Taking into account the current foreground/background state of the
+     *     app; apps whose mode varies by this state will always be reported
+     *     as {@link #MODE_ALLOWED}.</li>
+     * </ul>
+     *
+     * @param op The operation to check.  One of the OPSTR_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     * @throws SecurityException If the app has been configured to crash on this op.
+     */
+    public int unsafeCheckOp(@NonNull String op, int uid, @NonNull String packageName) {
+        return checkOp(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * @deprecated Renamed to {@link #unsafeCheckOp(String, int, String)}.
+     */
+    @Deprecated
+    public int checkOp(@NonNull String op, int uid, @NonNull String packageName) {
+        return checkOp(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * Like {@link #checkOp} but instead of throwing a {@link SecurityException} it
+     * returns {@link #MODE_ERRORED}.
+     */
+    public int unsafeCheckOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+        return checkOpNoThrow(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * @deprecated Renamed to {@link #unsafeCheckOpNoThrow(String, int, String)}.
+     */
+    @Deprecated
+    public int checkOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+        return checkOpNoThrow(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * Like {@link #checkOp} but returns the <em>raw</em> mode associated with the op.
+     * Does not throw a security exception, does not translate {@link #MODE_FOREGROUND}.
+     */
+    public int unsafeCheckOpRaw(@NonNull String op, int uid, @NonNull String packageName) {
+        try {
+            return mService.checkOperationRaw(strOpToOp(op), uid, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Like {@link #unsafeCheckOpNoThrow(String, int, String)} but returns the <em>raw</em>
+     * mode associated with the op. Does not throw a security exception, does not translate
+     * {@link #MODE_FOREGROUND}.
+     */
+    public int unsafeCheckOpRawNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+        try {
+            return mService.checkOperationRaw(strOpToOp(op), uid, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Make note of an application performing an operation.  Note that you must pass
+     * in both the uid and name of the application to be checked; this function will verify
+     * that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
+     * succeeds, the last execution time of the operation for this app will be updated to
+     * the current time.
+     * @param op The operation to note.  One of the OPSTR_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     * @throws SecurityException If the app has been configured to crash on this op.
+     */
+    public int noteOp(@NonNull String op, int uid, @NonNull String packageName) {
+        return noteOp(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
+     * returns {@link #MODE_ERRORED}.
+     */
+    public int noteOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+        return noteOpNoThrow(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * Make note of an application performing an operation on behalf of another
+     * application when handling an IPC. Note that you must pass the package name
+     * of the application that is being proxied while its UID will be inferred from
+     * the IPC state; this function will verify that the calling uid and proxied
+     * package name match, and if not, return {@link #MODE_IGNORED}. If this call
+     * succeeds, the last execution time of the operation for the proxied app and
+     * your app will be updated to the current time.
+     * @param op The operation to note.  One of the OPSTR_* constants.
+     * @param proxiedPackageName The name of the application calling into the proxy application.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     * @throws SecurityException If the app has been configured to crash on this op.
+     */
+    public int noteProxyOp(@NonNull String op, @NonNull String proxiedPackageName) {
+        return noteProxyOp(strOpToOp(op), proxiedPackageName);
+    }
+
+    /**
+     * Like {@link #noteProxyOp(String, String)} but instead
+     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * <p>This API requires the package with the {@code proxiedPackageName} to belongs to
+     * {@link Binder#getCallingUid()}.
+     */
+    public int noteProxyOpNoThrow(@NonNull String op, @NonNull String proxiedPackageName) {
+        return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName);
+    }
+
+    /**
+     * Like {@link #noteProxyOpNoThrow(String, String)} but allows to specify the proxied uid.
+     *
+     * <p>This API requires package with the {@code proxiedPackageName} to belong to
+     * {@code proxiedUid}.
+     *
+     * @param op The op to note
+     * @param proxiedPackageName The package to note the op for or {@code null} if the op should be
+     *                           noted for the "android" package
+     * @param proxiedUid The uid the package belongs to
+     */
+    public int noteProxyOpNoThrow(@NonNull String op, @Nullable String proxiedPackageName,
+            int proxiedUid) {
+        return noteProxyOpNoThrow(strOpToOp(op), proxiedPackageName, proxiedUid);
+    }
+
+    /**
+     * Report that an application has started executing a long-running operation.  Note that you
+     * must pass in both the uid and name of the application to be checked; this function will
+     * verify that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
+     * succeeds, the last execution time of the operation for this app will be updated to
+     * the current time and the operation will be marked as "running".  In this case you must
+     * later call {@link #finishOp(String, int, String)} to report when the application is no
+     * longer performing the operation.
+     * @param op The operation to start.  One of the OPSTR_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     * @throws SecurityException If the app has been configured to crash on this op.
+     */
+    public int startOp(@NonNull String op, int uid, @NonNull String packageName) {
+        return startOp(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * Like {@link #startOp} but instead of throwing a {@link SecurityException} it
+     * returns {@link #MODE_ERRORED}.
+     */
+    public int startOpNoThrow(@NonNull String op, int uid, @NonNull String packageName) {
+        return startOpNoThrow(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * Report that an application is no longer performing an operation that had previously
+     * been started with {@link #startOp(String, int, String)}.  There is no validation of input
+     * or result; the parameters supplied here must be the exact same ones previously passed
+     * in when starting the operation.
+     */
+    public void finishOp(@NonNull String op, int uid, @NonNull String packageName) {
+        finishOp(strOpToOp(op), uid, packageName);
+    }
+
+    /**
+     * Do a quick check for whether an application might be able to perform an operation.
+     * This is <em>not</em> a security check; you must use {@link #noteOp(int, int, String)}
+     * or {@link #startOp(int, int, String)} for your actual security checks, which also
+     * ensure that the given uid and package name are consistent.  This function can just be
+     * used for a quick check to see if an operation has been disabled for the application,
+     * as an early reject of some work.  This does not modify the time stamp or other data
+     * about the operation.
+     *
+     * <p>Important things this will not do (which you need to ultimate use
+     * {@link #noteOp(int, int, String)} or {@link #startOp(int, int, String)} to cover):</p>
+     * <ul>
+     *     <li>Verifying the uid and package are consistent, so callers can't spoof
+     *     their identity.</li>
+     *     <li>Taking into account the current foreground/background state of the
+     *     app; apps whose mode varies by this state will always be reported
+     *     as {@link #MODE_ALLOWED}.</li>
+     * </ul>
+     *
+     * @param op The operation to check.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     * @throws SecurityException If the app has been configured to crash on this op.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int checkOp(int op, int uid, String packageName) {
+        try {
+            int mode = mService.checkOperation(op, uid, packageName);
+            if (mode == MODE_ERRORED) {
+                throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+            }
+            return mode;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Like {@link #checkOp} but instead of throwing a {@link SecurityException} it
+     * returns {@link #MODE_ERRORED}.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int checkOpNoThrow(int op, int uid, String packageName) {
+        try {
+            int mode = mService.checkOperation(op, uid, packageName);
+            return mode == AppOpsManager.MODE_FOREGROUND ? AppOpsManager.MODE_ALLOWED : mode;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Do a quick check to validate if a package name belongs to a UID.
+     *
+     * @throws SecurityException if the package name doesn't belong to the given
+     *             UID, or if ownership cannot be verified.
+     */
+    public void checkPackage(int uid, @NonNull String packageName) {
+        try {
+            if (mService.checkPackage(uid, packageName) != MODE_ALLOWED) {
+                throw new SecurityException(
+                        "Package " + packageName + " does not belong to " + uid);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Like {@link #checkOp} but at a stream-level for audio operations.
+     * @hide
+     */
+    public int checkAudioOp(int op, int stream, int uid, String packageName) {
+        try {
+            final int mode = mService.checkAudioOperation(op, stream, uid, packageName);
+            if (mode == MODE_ERRORED) {
+                throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+            }
+            return mode;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Like {@link #checkAudioOp} but instead of throwing a {@link SecurityException} it
+     * returns {@link #MODE_ERRORED}.
+     * @hide
+     */
+    public int checkAudioOpNoThrow(int op, int stream, int uid, String packageName) {
+        try {
+            return mService.checkAudioOperation(op, stream, uid, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Make note of an application performing an operation.  Note that you must pass
+     * in both the uid and name of the application to be checked; this function will verify
+     * that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
+     * succeeds, the last execution time of the operation for this app will be updated to
+     * the current time.
+     * @param op The operation to note.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     * @throws SecurityException If the app has been configured to crash on this op.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int noteOp(int op, int uid, String packageName) {
+        final int mode = noteOpNoThrow(op, uid, packageName);
+        if (mode == MODE_ERRORED) {
+            throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+        }
+        return mode;
+    }
+
+    /**
+     * Make note of an application performing an operation on behalf of another
+     * application when handling an IPC. Note that you must pass the package name
+     * of the application that is being proxied while its UID will be inferred from
+     * the IPC state; this function will verify that the calling uid and proxied
+     * package name match, and if not, return {@link #MODE_IGNORED}. If this call
+     * succeeds, the last execution time of the operation for the proxied app and
+     * your app will be updated to the current time.
+     * @param op The operation to note. One of the OPSTR_* constants.
+     * @param proxiedPackageName The name of the application calling into the proxy application.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     * @throws SecurityException If the proxy or proxied app has been configured to
+     * crash on this op.
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int noteProxyOp(int op, String proxiedPackageName) {
+        int mode = noteProxyOpNoThrow(op, proxiedPackageName);
+        if (mode == MODE_ERRORED) {
+            throw new SecurityException("Proxy package " + mContext.getOpPackageName()
+                    + " from uid " + Process.myUid() + " or calling package "
+                    + proxiedPackageName + " from uid " + Binder.getCallingUid()
+                    + " not allowed to perform " + sOpNames[op]);
+        }
+        return mode;
+    }
+
+    /**
+     * Like {@link #noteProxyOp(int, String)} but instead
+     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     * @hide
+     */
+    public int noteProxyOpNoThrow(int op, String proxiedPackageName, int proxiedUid) {
+        try {
+            return mService.noteProxyOperation(op, Process.myUid(), mContext.getOpPackageName(),
+                    proxiedUid, proxiedPackageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Like {@link #noteProxyOp(int, String)} but instead
+     * of throwing a {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * <p>This API requires the package with {@code proxiedPackageName} to belongs to
+     * {@link Binder#getCallingUid()}.
+     *
+     * @hide
+     */
+    public int noteProxyOpNoThrow(int op, String proxiedPackageName) {
+        return noteProxyOpNoThrow(op, proxiedPackageName, Binder.getCallingUid());
+    }
+
+    /**
+     * Like {@link #noteOp} but instead of throwing a {@link SecurityException} it
+     * returns {@link #MODE_ERRORED}.
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public int noteOpNoThrow(int op, int uid, String packageName) {
+        try {
+            return mService.noteOperation(op, uid, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public int noteOp(int op) {
+        return noteOp(op, Process.myUid(), mContext.getOpPackageName());
+    }
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public static IBinder getToken(IAppOpsService service) {
+        synchronized (AppOpsManager.class) {
+            if (sToken != null) {
+                return sToken;
+            }
+            try {
+                sToken = service.getToken(new Binder());
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            return sToken;
+        }
+    }
+
+    /** @hide */
+    public int startOp(int op) {
+        return startOp(op, Process.myUid(), mContext.getOpPackageName());
+    }
+
+    /**
+     * Report that an application has started executing a long-running operation.  Note that you
+     * must pass in both the uid and name of the application to be checked; this function will
+     * verify that these two match, and if not, return {@link #MODE_IGNORED}.  If this call
+     * succeeds, the last execution time of the operation for this app will be updated to
+     * the current time and the operation will be marked as "running".  In this case you must
+     * later call {@link #finishOp(int, int, String)} to report when the application is no
+     * longer performing the operation.
+     *
+     * @param op The operation to start.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     * @throws SecurityException If the app has been configured to crash on this op.
+     * @hide
+     */
+    public int startOp(int op, int uid, String packageName) {
+        return startOp(op, uid, packageName, false);
+    }
+
+    /**
+     * Report that an application has started executing a long-running operation. Similar
+     * to {@link #startOp(String, int, String) except that if the mode is {@link #MODE_DEFAULT}
+     * the operation should succeed since the caller has performed its standard permission
+     * checks which passed and would perform the protected operation for this mode.
+     *
+     * @param op The operation to start.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+     *
+     * @throws SecurityException If the app has been configured to crash on this op or
+     * the package is not in the passed in UID.
+     *
+     * @hide
+     */
+    public int startOp(int op, int uid, String packageName, boolean startIfModeDefault) {
+        final int mode = startOpNoThrow(op, uid, packageName, startIfModeDefault);
+        if (mode == MODE_ERRORED) {
+            throw new SecurityException(buildSecurityExceptionMsg(op, uid, packageName));
+        }
+        return mode;
+    }
+
+    /**
+     * Like {@link #startOp} but instead of throwing a {@link SecurityException} it
+     * returns {@link #MODE_ERRORED}.
+     * @hide
+     */
+    public int startOpNoThrow(int op, int uid, String packageName) {
+        return startOpNoThrow(op, uid, packageName, false);
+    }
+
+    /**
+     * Like {@link #startOp(int, int, String, boolean)} but instead of throwing a
+     * {@link SecurityException} it returns {@link #MODE_ERRORED}.
+     *
+     * @param op The operation to start.  One of the OP_* constants.
+     * @param uid The user id of the application attempting to perform the operation.
+     * @param packageName The name of the application attempting to perform the operation.
+     * @return Returns {@link #MODE_ALLOWED} if the operation is allowed, or
+     * {@link #MODE_IGNORED} if it is not allowed and should be silently ignored (without
+     * causing the app to crash).
+     * @param startIfModeDefault Whether to start if mode is {@link #MODE_DEFAULT}.
+     *
+     * @hide
+     */
+    public int startOpNoThrow(int op, int uid, String packageName, boolean startIfModeDefault) {
+        try {
+            return mService.startOperation(getToken(mService), op, uid, packageName,
+                    startIfModeDefault);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Report that an application is no longer performing an operation that had previously
+     * been started with {@link #startOp(int, int, String)}.  There is no validation of input
+     * or result; the parameters supplied here must be the exact same ones previously passed
+     * in when starting the operation.
+     * @hide
+     */
+    public void finishOp(int op, int uid, String packageName) {
+        try {
+            mService.finishOperation(getToken(mService), op, uid, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    public void finishOp(int op) {
+        finishOp(op, Process.myUid(), mContext.getOpPackageName());
+    }
+
+    /**
+     * Checks whether the given op for a UID and package is active.
+     *
+     * <p> If you don't hold the {@link android.Manifest.permission#WATCH_APPOPS} permission
+     * you can query only for your UID.
+     *
+     * @see #startWatchingActive(int[], OnOpActiveChangedListener)
+     * @see #stopWatchingMode(OnOpChangedListener)
+     * @see #finishOp(int)
+     * @see #startOp(int)
+     *
+     * @hide */
+    @TestApi
+    // TODO: Uncomment below annotation once b/73559440 is fixed
+    // @RequiresPermission(value=Manifest.permission.WATCH_APPOPS, conditional=true)
+    public boolean isOperationActive(int code, int uid, String packageName) {
+        try {
+            return mService.isOperationActive(code, uid, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Configures the app ops persistence for testing.
+     *
+     * @param mode The mode in which the historical registry operates.
+     * @param baseSnapshotInterval The base interval on which we would be persisting a snapshot of
+     *   the historical data. The history is recursive where every subsequent step encompasses
+     *   {@code compressionStep} longer interval with {@code compressionStep} distance between
+     *    snapshots.
+     * @param compressionStep The compression step in every iteration.
+     *
+     * @see #HISTORICAL_MODE_DISABLED
+     * @see #HISTORICAL_MODE_ENABLED_ACTIVE
+     * @see #HISTORICAL_MODE_ENABLED_PASSIVE
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void setHistoryParameters(@HistoricalMode int mode, long baseSnapshotInterval,
+            int compressionStep) {
+        try {
+            mService.setHistoryParameters(mode, baseSnapshotInterval, compressionStep);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Offsets the history by the given duration.
+     *
+     * @param offsetMillis The offset duration.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void offsetHistory(long offsetMillis) {
+        try {
+            mService.offsetHistory(offsetMillis);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Adds ops to the history directly. This could be useful for testing especially
+     * when the historical registry operates in {@link #HISTORICAL_MODE_ENABLED_PASSIVE}
+     * mode.
+     *
+     * @param ops The ops to add to the history.
+     *
+     * @see #setHistoryParameters(int, long, int)
+     * @see #HISTORICAL_MODE_ENABLED_PASSIVE
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void addHistoricalOps(@NonNull HistoricalOps ops) {
+        try {
+            mService.addHistoricalOps(ops);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Resets the app ops persistence for testing.
+     *
+     * @see #setHistoryParameters(int, long, int)
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void resetHistoryParameters() {
+        try {
+            mService.resetHistoryParameters();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Clears all app ops history.
+     *
+     * @hide
+     */
+    @TestApi
+    @RequiresPermission(Manifest.permission.MANAGE_APPOPS)
+    public void clearHistory() {
+        try {
+            mService.clearHistory();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns all supported operation names.
+     * @hide
+     */
+    @SystemApi
+    @TestApi
+    public static String[] getOpStrs() {
+        return Arrays.copyOf(sOpToString, sOpToString.length);
+    }
+
+
+    /**
+     * @return number of App ops
+     * @hide
+     */
+    @TestApi
+    public static int getNumOps() {
+        return _NUM_OP;
+    }
+
+    /**
+     * Computes the max for the given flags in between the begin and
+     * end UID states.
+     *
+     * @param counts The data array.
+     * @param flags The UID flags.
+     * @param beginUidState The beginning UID state (exclusive).
+     * @param endUidState The end UID state.
+     * @return The sum.
+     */
+    private static long maxForFlagsInStates(@Nullable LongSparseLongArray counts,
+            @UidState int beginUidState, @UidState int endUidState,
+            @OpFlags int flags) {
+        if (counts == null) {
+            return 0;
+        }
+        long max = 0;
+        while (flags != 0) {
+            final int flag = 1 << Integer.numberOfTrailingZeros(flags);
+            flags &= ~flag;
+            for (int uidState : UID_STATES) {
+                if (uidState < beginUidState || uidState > endUidState) {
+                    continue;
+                }
+                final long key = makeKey(uidState, flag);
+                max = Math.max(max, counts.get(key));
+            }
+        }
+        return max;
+    }
+
+
+    private static void writeLongSparseLongArrayToParcel(
+            @Nullable LongSparseLongArray array, @NonNull Parcel parcel) {
+        if (array != null) {
+            final int size = array.size();
+            parcel.writeInt(size);
+            for (int i = 0; i < size; i++) {
+                parcel.writeLong(array.keyAt(i));
+                parcel.writeLong(array.valueAt(i));
+            }
+        } else {
+            parcel.writeInt(-1);
+        }
+    }
+
+    private static @Nullable LongSparseLongArray readLongSparseLongArrayFromParcel(
+            @NonNull Parcel parcel) {
+        final int size = parcel.readInt();
+        if (size < 0) {
+            return null;
+        }
+        final LongSparseLongArray array = new LongSparseLongArray(size);
+        for (int i = 0; i < size; i++) {
+            array.append(parcel.readLong(), parcel.readLong());
+        }
+        return array;
+    }
+
+    private static void writeLongSparseStringArrayToParcel(
+            @Nullable LongSparseArray<String> array, @NonNull Parcel parcel) {
+        if (array != null) {
+            final int size = array.size();
+            parcel.writeInt(size);
+            for (int i = 0; i < size; i++) {
+                parcel.writeLong(array.keyAt(i));
+                parcel.writeString(array.valueAt(i));
+            }
+        } else {
+            parcel.writeInt(-1);
+        }
+    }
+
+    private static @Nullable LongSparseArray<String> readLongSparseStringArrayFromParcel(
+            @NonNull Parcel parcel) {
+        final int size = parcel.readInt();
+        if (size < 0) {
+            return null;
+        }
+        final LongSparseArray<String> array = new LongSparseArray<>(size);
+        for (int i = 0; i < size; i++) {
+            array.append(parcel.readLong(), parcel.readString());
+        }
+        return array;
+    }
+
+    /**
+     * Collects the keys from an array to the result creating the result if needed.
+     *
+     * @param array The array whose keys to collect.
+     * @param result The optional result store collected keys.
+     * @return The result collected keys array.
+     */
+    private static LongSparseArray<Object> collectKeys(@Nullable LongSparseLongArray array,
+            @Nullable LongSparseArray<Object> result) {
+        if (array != null) {
+            if (result == null) {
+                result = new LongSparseArray<>();
+            }
+            final int accessSize = array.size();
+            for (int i = 0; i < accessSize; i++) {
+                result.put(array.keyAt(i), null);
+            }
+        }
+        return result;
+    }
+
+    /** @hide */
+    public static String uidStateToString(@UidState int uidState) {
+        switch (uidState) {
+            case UID_STATE_PERSISTENT: {
+                return "UID_STATE_PERSISTENT";
+            }
+            case UID_STATE_TOP: {
+                return "UID_STATE_TOP";
+            }
+            case UID_STATE_FOREGROUND_SERVICE_LOCATION: {
+                return "UID_STATE_FOREGROUND_SERVICE_LOCATION";
+            }
+            case UID_STATE_FOREGROUND_SERVICE: {
+                return "UID_STATE_FOREGROUND_SERVICE";
+            }
+            case UID_STATE_FOREGROUND: {
+                return "UID_STATE_FOREGROUND";
+            }
+            case UID_STATE_BACKGROUND: {
+                return "UID_STATE_BACKGROUND";
+            }
+            case UID_STATE_CACHED: {
+                return "UID_STATE_CACHED";
+            }
+            default: {
+                return "UNKNOWN";
+            }
+        }
+    }
+
+    /** @hide */
+    public static int parseHistoricalMode(@NonNull String mode) {
+        switch (mode) {
+            case "HISTORICAL_MODE_ENABLED_ACTIVE": {
+                return HISTORICAL_MODE_ENABLED_ACTIVE;
+            }
+            case "HISTORICAL_MODE_ENABLED_PASSIVE": {
+                return HISTORICAL_MODE_ENABLED_PASSIVE;
+            }
+            default: {
+                return HISTORICAL_MODE_DISABLED;
+            }
+        }
+    }
+
+    /** @hide */
+    public static String historicalModeToString(@HistoricalMode int mode) {
+        switch (mode) {
+            case HISTORICAL_MODE_DISABLED: {
+                return "HISTORICAL_MODE_DISABLED";
+            }
+            case HISTORICAL_MODE_ENABLED_ACTIVE: {
+                return "HISTORICAL_MODE_ENABLED_ACTIVE";
+            }
+            case HISTORICAL_MODE_ENABLED_PASSIVE: {
+                return "HISTORICAL_MODE_ENABLED_PASSIVE";
+            }
+            default: {
+                return "UNKNOWN";
+            }
+        }
+    }
+
+    private static int getSystemAlertWindowDefault() {
+        final Context context = ActivityThread.currentApplication();
+        if (context == null) {
+            return AppOpsManager.MODE_DEFAULT;
+        }
+
+        // system alert window is disable on low ram phones starting from Q
+        final PackageManager pm = context.getPackageManager();
+        // TVs are constantly plugged in and has less concern for memory/power
+        if (ActivityManager.isLowRamDeviceStatic()
+                && !pm.hasSystemFeature(PackageManager.FEATURE_LEANBACK, 0)) {
+            return AppOpsManager.MODE_IGNORED;
+        }
+
+        return AppOpsManager.MODE_DEFAULT;
+    }
+}
diff --git a/android/app/AppOpsManagerInternal.java b/android/app/AppOpsManagerInternal.java
new file mode 100644
index 0000000..08cad04
--- /dev/null
+++ b/android/app/AppOpsManagerInternal.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.NonNull;
+import android.util.SparseIntArray;
+
+import com.android.internal.util.function.QuadFunction;
+import com.android.internal.util.function.TriFunction;
+
+/**
+ * App ops service local interface.
+ *
+ * @hide Only for use within the system server.
+ */
+public abstract class AppOpsManagerInternal {
+    /** Interface to override app ops checks via composition */
+    public interface CheckOpsDelegate {
+        /**
+         * Allows overriding check operation behavior.
+         *
+         * @param code The op code to check.
+         * @param uid The UID for which to check.
+         * @param packageName The package for which to check.
+         * @param superImpl The super implementation.
+         * @param raw Whether to check the raw op i.e. not interpret the mode based on UID state.
+         * @return The app op check result.
+         */
+        int checkOperation(int code, int uid, String packageName, boolean raw,
+                QuadFunction<Integer, Integer, String, Boolean, Integer> superImpl);
+
+        /**
+         * Allows overriding check audio operation behavior.
+         *
+         * @param code The op code to check.
+         * @param usage The audio op usage.
+         * @param uid The UID for which to check.
+         * @param packageName The package for which to check.
+         * @param superImpl The super implementation.
+         * @return The app op check result.
+         */
+        int checkAudioOperation(int code, int usage, int uid, String packageName,
+                QuadFunction<Integer, Integer, Integer, String, Integer> superImpl);
+
+        /**
+         * Allows overriding note operation behavior.
+         *
+         * @param code The op code to note.
+         * @param uid The UID for which to note.
+         * @param packageName The package for which to note.
+         * @param superImpl The super implementation.
+         * @return The app op note result.
+         */
+        int noteOperation(int code, int uid, String packageName,
+                TriFunction<Integer, Integer, String, Integer> superImpl);
+    }
+
+    /**
+     * Set the currently configured device and profile owners.  Specifies the package uid (value)
+     * that has been configured for each user (key) that has one.  These will be allowed privileged
+     * access to app ops for their user.
+     */
+    public abstract void setDeviceAndProfileOwners(SparseIntArray owners);
+
+    /**
+     * Sets the app-ops mode for a certain app-op and uid.
+     *
+     * <p>Similar as {@link AppOpsManager#setUidMode} but does not require the package manager to be
+     * working. Hence this can be used very early during boot.
+     *
+     * <p>Only for internal callers. Does <u>not</u> verify that package name belongs to uid.
+     *
+     * @param code The op code to set.
+     * @param uid The UID for which to set.
+     * @param mode The new mode to set.
+     */
+    public abstract void setUidMode(int code, int uid, int mode);
+
+    /**
+     * Set all {@link #setMode (package) modes} for this uid to the default value.
+     *
+     * @param code The app-op
+     * @param uid The uid
+     */
+    public abstract void setAllPkgModesToDefault(int code, int uid);
+
+    /**
+     * Get the (raw) mode of an app-op.
+     *
+     * <p>Does <u>not</u> verify that package belongs to uid. The caller needs to do that.
+     *
+     * @param code The code of the op
+     * @param uid The uid of the package the op belongs to
+     * @param packageName The package the op belongs to
+     *
+     * @return The mode of the op
+     */
+    public abstract @AppOpsManager.Mode int checkOperationUnchecked(int code, int uid,
+            @NonNull String packageName);
+}
diff --git a/android/app/Application.java b/android/app/Application.java
new file mode 100644
index 0000000..e12942f
--- /dev/null
+++ b/android/app/Application.java
@@ -0,0 +1,655 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.CallSuper;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UnsupportedAppUsage;
+import android.content.ComponentCallbacks;
+import android.content.ComponentCallbacks2;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.autofill.AutofillManager;
+
+import java.util.ArrayList;
+
+/**
+ * Base class for maintaining global application state. You can provide your own
+ * implementation by creating a subclass and specifying the fully-qualified name
+ * of this subclass as the <code>"android:name"</code> attribute in your
+ * AndroidManifest.xml's <code>&lt;application&gt;</code> tag. The Application
+ * class, or your subclass of the Application class, is instantiated before any
+ * other class when the process for your application/package is created.
+ *
+ * <p class="note"><strong>Note: </strong>There is normally no need to subclass
+ * Application.  In most situations, static singletons can provide the same
+ * functionality in a more modular way.  If your singleton needs a global
+ * context (for example to register broadcast receivers), include
+ * {@link android.content.Context#getApplicationContext() Context.getApplicationContext()}
+ * as a {@link android.content.Context} argument when invoking your singleton's
+ * <code>getInstance()</code> method.
+ * </p>
+ */
+public class Application extends ContextWrapper implements ComponentCallbacks2 {
+    private static final String TAG = "Application";
+    @UnsupportedAppUsage
+    private ArrayList<ComponentCallbacks> mComponentCallbacks =
+            new ArrayList<ComponentCallbacks>();
+    @UnsupportedAppUsage
+    private ArrayList<ActivityLifecycleCallbacks> mActivityLifecycleCallbacks =
+            new ArrayList<ActivityLifecycleCallbacks>();
+    @UnsupportedAppUsage
+    private ArrayList<OnProvideAssistDataListener> mAssistCallbacks = null;
+
+    /** @hide */
+    @UnsupportedAppUsage
+    public LoadedApk mLoadedApk;
+
+    public interface ActivityLifecycleCallbacks {
+
+        /**
+         * Called as the first step of the Activity being created. This is always called before
+         * {@link Activity#onCreate}.
+         */
+        default void onActivityPreCreated(@NonNull Activity activity,
+                @Nullable Bundle savedInstanceState) {
+        }
+
+        /**
+         * Called when the Activity calls {@link Activity#onCreate super.onCreate()}.
+         */
+        void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState);
+
+        /**
+         * Called as the last step of the Activity being created. This is always called after
+         * {@link Activity#onCreate}.
+         */
+        default void onActivityPostCreated(@NonNull Activity activity,
+                @Nullable Bundle savedInstanceState) {
+        }
+
+        /**
+         * Called as the first step of the Activity being started. This is always called before
+         * {@link Activity#onStart}.
+         */
+        default void onActivityPreStarted(@NonNull Activity activity) {
+        }
+
+        /**
+         * Called when the Activity calls {@link Activity#onStart super.onStart()}.
+         */
+        void onActivityStarted(@NonNull Activity activity);
+
+        /**
+         * Called as the last step of the Activity being started. This is always called after
+         * {@link Activity#onStart}.
+         */
+        default void onActivityPostStarted(@NonNull Activity activity) {
+        }
+
+        /**
+         * Called as the first step of the Activity being resumed. This is always called before
+         * {@link Activity#onResume}.
+         */
+        default void onActivityPreResumed(@NonNull Activity activity) {
+        }
+
+        /**
+         * Called when the Activity calls {@link Activity#onResume super.onResume()}.
+         */
+        void onActivityResumed(@NonNull Activity activity);
+
+        /**
+         * Called as the last step of the Activity being resumed. This is always called after
+         * {@link Activity#onResume} and {@link Activity#onPostResume}.
+         */
+        default void onActivityPostResumed(@NonNull Activity activity) {
+        }
+
+        /**
+         * Called as the first step of the Activity being paused. This is always called before
+         * {@link Activity#onPause}.
+         */
+        default void onActivityPrePaused(@NonNull Activity activity) {
+        }
+
+        /**
+         * Called when the Activity calls {@link Activity#onPause super.onPause()}.
+         */
+        void onActivityPaused(@NonNull Activity activity);
+
+        /**
+         * Called as the last step of the Activity being paused. This is always called after
+         * {@link Activity#onPause}.
+         */
+        default void onActivityPostPaused(@NonNull Activity activity) {
+        }
+
+        /**
+         * Called as the first step of the Activity being stopped. This is always called before
+         * {@link Activity#onStop}.
+         */
+        default void onActivityPreStopped(@NonNull Activity activity) {
+        }
+
+        /**
+         * Called when the Activity calls {@link Activity#onStop super.onStop()}.
+         */
+        void onActivityStopped(@NonNull Activity activity);
+
+        /**
+         * Called as the last step of the Activity being stopped. This is always called after
+         * {@link Activity#onStop}.
+         */
+        default void onActivityPostStopped(@NonNull Activity activity) {
+        }
+
+        /**
+         * Called as the first step of the Activity saving its instance state. This is always
+         * called before {@link Activity#onSaveInstanceState}.
+         */
+        default void onActivityPreSaveInstanceState(@NonNull Activity activity,
+                @NonNull Bundle outState) {
+        }
+
+        /**
+         * Called when the Activity calls
+         * {@link Activity#onSaveInstanceState super.onSaveInstanceState()}.
+         */
+        void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState);
+
+        /**
+         * Called as the last step of the Activity saving its instance state. This is always
+         * called after{@link Activity#onSaveInstanceState}.
+         */
+        default void onActivityPostSaveInstanceState(@NonNull Activity activity,
+                @NonNull Bundle outState) {
+        }
+
+        /**
+         * Called as the first step of the Activity being destroyed. This is always called before
+         * {@link Activity#onDestroy}.
+         */
+        default void onActivityPreDestroyed(@NonNull Activity activity) {
+        }
+
+        /**
+         * Called when the Activity calls {@link Activity#onDestroy super.onDestroy()}.
+         */
+        void onActivityDestroyed(@NonNull Activity activity);
+
+        /**
+         * Called as the last step of the Activity being destroyed. This is always called after
+         * {@link Activity#onDestroy}.
+         */
+        default void onActivityPostDestroyed(@NonNull Activity activity) {
+        }
+    }
+
+    /**
+     * Callback interface for use with {@link Application#registerOnProvideAssistDataListener}
+     * and {@link Application#unregisterOnProvideAssistDataListener}.
+     */
+    public interface OnProvideAssistDataListener {
+        /**
+         * This is called when the user is requesting an assist, to build a full
+         * {@link Intent#ACTION_ASSIST} Intent with all of the context of the current
+         * application.  You can override this method to place into the bundle anything
+         * you would like to appear in the {@link Intent#EXTRA_ASSIST_CONTEXT} part
+         * of the assist Intent.
+         */
+        public void onProvideAssistData(Activity activity, Bundle data);
+    }
+
+    public Application() {
+        super(null);
+    }
+
+    /**
+     * Called when the application is starting, before any activity, service,
+     * or receiver objects (excluding content providers) have been created.
+     *
+     * <p>Implementations should be as quick as possible (for example using
+     * lazy initialization of state) since the time spent in this function
+     * directly impacts the performance of starting the first activity,
+     * service, or receiver in a process.</p>
+     *
+     * <p>If you override this method, be sure to call {@code super.onCreate()}.</p>
+     *
+     * <p class="note">Be aware that direct boot may also affect callback order on
+     * Android {@link android.os.Build.VERSION_CODES#N} and later devices.
+     * Until the user unlocks the device, only direct boot aware components are
+     * allowed to run. You should consider that all direct boot unaware
+     * components, including such {@link android.content.ContentProvider}, are
+     * disabled until user unlock happens, especially when component callback
+     * order matters.</p>
+     */
+    @CallSuper
+    public void onCreate() {
+    }
+
+    /**
+     * This method is for use in emulated process environments.  It will
+     * never be called on a production Android device, where processes are
+     * removed by simply killing them; no user code (including this callback)
+     * is executed when doing so.
+     */
+    @CallSuper
+    public void onTerminate() {
+    }
+
+    @CallSuper
+    public void onConfigurationChanged(@NonNull Configuration newConfig) {
+        Object[] callbacks = collectComponentCallbacks();
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                ((ComponentCallbacks)callbacks[i]).onConfigurationChanged(newConfig);
+            }
+        }
+    }
+
+    @CallSuper
+    public void onLowMemory() {
+        Object[] callbacks = collectComponentCallbacks();
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                ((ComponentCallbacks)callbacks[i]).onLowMemory();
+            }
+        }
+    }
+
+    @CallSuper
+    public void onTrimMemory(int level) {
+        Object[] callbacks = collectComponentCallbacks();
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                Object c = callbacks[i];
+                if (c instanceof ComponentCallbacks2) {
+                    ((ComponentCallbacks2)c).onTrimMemory(level);
+                }
+            }
+        }
+    }
+
+    public void registerComponentCallbacks(ComponentCallbacks callback) {
+        synchronized (mComponentCallbacks) {
+            mComponentCallbacks.add(callback);
+        }
+    }
+
+    public void unregisterComponentCallbacks(ComponentCallbacks callback) {
+        synchronized (mComponentCallbacks) {
+            mComponentCallbacks.remove(callback);
+        }
+    }
+
+    public void registerActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
+        synchronized (mActivityLifecycleCallbacks) {
+            mActivityLifecycleCallbacks.add(callback);
+        }
+    }
+
+    public void unregisterActivityLifecycleCallbacks(ActivityLifecycleCallbacks callback) {
+        synchronized (mActivityLifecycleCallbacks) {
+            mActivityLifecycleCallbacks.remove(callback);
+        }
+    }
+
+    public void registerOnProvideAssistDataListener(OnProvideAssistDataListener callback) {
+        synchronized (this) {
+            if (mAssistCallbacks == null) {
+                mAssistCallbacks = new ArrayList<OnProvideAssistDataListener>();
+            }
+            mAssistCallbacks.add(callback);
+        }
+    }
+
+    public void unregisterOnProvideAssistDataListener(OnProvideAssistDataListener callback) {
+        synchronized (this) {
+            if (mAssistCallbacks != null) {
+                mAssistCallbacks.remove(callback);
+            }
+        }
+    }
+
+    /**
+     * Returns the name of the current process. A package's default process name
+     * is the same as its package name. Non-default processes will look like
+     * "$PACKAGE_NAME:$NAME", where $NAME corresponds to an android:process
+     * attribute within AndroidManifest.xml.
+     */
+    public static String getProcessName() {
+        return ActivityThread.currentProcessName();
+    }
+
+    // ------------------ Internal API ------------------
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    /* package */ final void attach(Context context) {
+        attachBaseContext(context);
+        mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPreCreated(@NonNull Activity activity,
+            @Nullable Bundle savedInstanceState) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreCreated(activity,
+                        savedInstanceState);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    /* package */ void dispatchActivityCreated(@NonNull Activity activity,
+            @Nullable Bundle savedInstanceState) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks)callbacks[i]).onActivityCreated(activity,
+                        savedInstanceState);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPostCreated(@NonNull Activity activity,
+            @Nullable Bundle savedInstanceState) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostCreated(activity,
+                        savedInstanceState);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPreStarted(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreStarted(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    /* package */ void dispatchActivityStarted(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks)callbacks[i]).onActivityStarted(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPostStarted(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostStarted(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPreResumed(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreResumed(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    /* package */ void dispatchActivityResumed(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks)callbacks[i]).onActivityResumed(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPostResumed(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostResumed(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPrePaused(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPrePaused(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    /* package */ void dispatchActivityPaused(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks)callbacks[i]).onActivityPaused(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPostPaused(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostPaused(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPreStopped(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreStopped(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    /* package */ void dispatchActivityStopped(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks)callbacks[i]).onActivityStopped(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPostStopped(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostStopped(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPreSaveInstanceState(@NonNull Activity activity,
+            @NonNull Bundle outState) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreSaveInstanceState(
+                        activity, outState);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    /* package */ void dispatchActivitySaveInstanceState(@NonNull Activity activity,
+            @NonNull Bundle outState) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks)callbacks[i]).onActivitySaveInstanceState(activity,
+                        outState);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPostSaveInstanceState(@NonNull Activity activity,
+            @NonNull Bundle outState) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostSaveInstanceState(
+                        activity, outState);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPreDestroyed(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPreDestroyed(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    /* package */ void dispatchActivityDestroyed(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks)callbacks[i]).onActivityDestroyed(activity);
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+        /* package */ void dispatchActivityPostDestroyed(@NonNull Activity activity) {
+        Object[] callbacks = collectActivityLifecycleCallbacks();
+        if (callbacks != null) {
+            for (int i = 0; i < callbacks.length; i++) {
+                ((ActivityLifecycleCallbacks) callbacks[i]).onActivityPostDestroyed(activity);
+            }
+        }
+    }
+
+    private Object[] collectComponentCallbacks() {
+        Object[] callbacks = null;
+        synchronized (mComponentCallbacks) {
+            if (mComponentCallbacks.size() > 0) {
+                callbacks = mComponentCallbacks.toArray();
+            }
+        }
+        return callbacks;
+    }
+
+    @UnsupportedAppUsage
+    private Object[] collectActivityLifecycleCallbacks() {
+        Object[] callbacks = null;
+        synchronized (mActivityLifecycleCallbacks) {
+            if (mActivityLifecycleCallbacks.size() > 0) {
+                callbacks = mActivityLifecycleCallbacks.toArray();
+            }
+        }
+        return callbacks;
+    }
+
+    /* package */ void dispatchOnProvideAssistData(Activity activity, Bundle data) {
+        Object[] callbacks;
+        synchronized (this) {
+            if (mAssistCallbacks == null) {
+                return;
+            }
+            callbacks = mAssistCallbacks.toArray();
+        }
+        if (callbacks != null) {
+            for (int i=0; i<callbacks.length; i++) {
+                ((OnProvideAssistDataListener)callbacks[i]).onProvideAssistData(activity, data);
+            }
+        }
+    }
+
+    /** @hide */
+    @Override
+    public AutofillManager.AutofillClient getAutofillClient() {
+        final AutofillManager.AutofillClient client = super.getAutofillClient();
+        if (client != null) {
+            return client;
+        }
+        if (android.view.autofill.Helper.sVerbose) {
+            Log.v(TAG, "getAutofillClient(): null on super, trying to find activity thread");
+        }
+        // Okay, ppl use the application context when they should not. This breaks
+        // autofill among other things. We pick the focused activity since autofill
+        // interacts only with the currently focused activity and we need the fill
+        // client only if a call comes from the focused activity. Sigh...
+        final ActivityThread activityThread = ActivityThread.currentActivityThread();
+        if (activityThread == null) {
+            return null;
+        }
+        final int activityCount = activityThread.mActivities.size();
+        for (int i = 0; i < activityCount; i++) {
+            final ActivityThread.ActivityClientRecord record =
+                    activityThread.mActivities.valueAt(i);
+            if (record == null) {
+                continue;
+            }
+            final Activity activity = record.activity;
+            if (activity == null) {
+                continue;
+            }
+            if (activity.getWindow().getDecorView().hasFocus()) {
+                if (android.view.autofill.Helper.sVerbose) {
+                    Log.v(TAG, "getAutofillClient(): found activity for " + this + ": " + activity);
+                }
+                return activity;
+            }
+        }
+        if (android.view.autofill.Helper.sVerbose) {
+            Log.v(TAG, "getAutofillClient(): none of the " + activityCount + " activities on "
+                    + this + " have focus");
+        }
+        return null;
+    }
+}
diff --git a/android/app/ApplicationErrorReport.java b/android/app/ApplicationErrorReport.java
new file mode 100644
index 0000000..a0b3dc0
--- /dev/null
+++ b/android/app/ApplicationErrorReport.java
@@ -0,0 +1,710 @@
+/*
+ * Copyright (C) 2008 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Binder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.util.Printer;
+import android.util.Slog;
+
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Describes an application error.
+ *
+ * A report has a type, which is one of
+ * <ul>
+ * <li> {@link #TYPE_NONE} uninitialized instance of {@link ApplicationErrorReport}.
+ * <li> {@link #TYPE_CRASH} application crash. Information about the crash
+ * is stored in {@link #crashInfo}.
+ * <li> {@link #TYPE_ANR} application not responding. Information about the
+ * ANR is stored in {@link #anrInfo}.
+ * <li> {@link #TYPE_BATTERY} user reported application is using too much
+ * battery. Information about the battery use is stored in {@link #batteryInfo}.
+ * <li> {@link #TYPE_RUNNING_SERVICE} user reported application is leaving an
+ * unneeded serive running. Information about the battery use is stored in
+ * {@link #runningServiceInfo}.
+ * </ul>
+ */
+
+public class ApplicationErrorReport implements Parcelable {
+    // System property defining error report receiver for system apps
+    static final String SYSTEM_APPS_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.system.apps";
+
+    // System property defining default error report receiver
+    static final String DEFAULT_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.default";
+
+    /**
+     * Uninitialized error report.
+     */
+    public static final int TYPE_NONE = 0;
+
+    /**
+     * An error report about an application crash.
+     */
+    public static final int TYPE_CRASH = 1;
+
+    /**
+     * An error report about an application that's not responding.
+     */
+    public static final int TYPE_ANR = 2;
+
+    /**
+     * An error report about an application that's consuming too much battery.
+     */
+    public static final int TYPE_BATTERY = 3;
+
+    /**
+     * A report from a user to a developer about a running service that the
+     * user doesn't think should be running.
+     */
+    public static final int TYPE_RUNNING_SERVICE = 5;
+
+    /**
+     * Type of this report. Can be one of {@link #TYPE_NONE},
+     * {@link #TYPE_CRASH}, {@link #TYPE_ANR}, {@link #TYPE_BATTERY},
+     * or {@link #TYPE_RUNNING_SERVICE}.
+     */
+    public int type;
+
+    /**
+     * Package name of the application.
+     */
+    public String packageName;
+
+    /**
+     * Package name of the application which installed the application this
+     * report pertains to.
+     * This identifies which market the application came from.
+     */
+    public String installerPackageName;
+
+    /**
+     * Process name of the application.
+     */
+    public String processName;
+
+    /**
+     * Time at which the error occurred.
+     */
+    public long time;
+
+    /**
+     * Set if the app is on the system image.
+     */
+    public boolean systemApp;
+
+    /**
+     * If this report is of type {@link #TYPE_CRASH}, contains an instance
+     * of CrashInfo describing the crash; otherwise null.
+     */
+    public CrashInfo crashInfo;
+
+    /**
+     * If this report is of type {@link #TYPE_ANR}, contains an instance
+     * of AnrInfo describing the ANR; otherwise null.
+     */
+    public AnrInfo anrInfo;
+
+    /**
+     * If this report is of type {@link #TYPE_BATTERY}, contains an instance
+     * of BatteryInfo; otherwise null.
+     */
+    public BatteryInfo batteryInfo;
+
+    /**
+     * If this report is of type {@link #TYPE_RUNNING_SERVICE}, contains an instance
+     * of RunningServiceInfo; otherwise null.
+     */
+    public RunningServiceInfo runningServiceInfo;
+
+    /**
+     * Create an uninitialized instance of {@link ApplicationErrorReport}.
+     */
+    public ApplicationErrorReport() {
+    }
+
+    /**
+     * Create an instance of {@link ApplicationErrorReport} initialized from
+     * a parcel.
+     */
+    ApplicationErrorReport(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public static ComponentName getErrorReportReceiver(Context context,
+            String packageName, int appFlags) {
+        // check if error reporting is enabled in secure settings
+        int enabled = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.SEND_ACTION_APP_ERROR, 0);
+        if (enabled == 0) {
+            return null;
+        }
+
+        PackageManager pm = context.getPackageManager();
+
+        // look for receiver in the installer package
+        String candidate = null;
+        ComponentName result = null;
+
+        try {
+            candidate = pm.getInstallerPackageName(packageName);
+        } catch (IllegalArgumentException e) {
+            // the package could already removed
+        }
+
+        if (candidate != null) {
+            result = getErrorReportReceiver(pm, packageName, candidate);
+            if (result != null) {
+                return result;
+            }
+        }
+
+        // if the error app is on the system image, look for system apps
+        // error receiver
+        if ((appFlags&ApplicationInfo.FLAG_SYSTEM) != 0) {
+            candidate = SystemProperties.get(SYSTEM_APPS_ERROR_RECEIVER_PROPERTY);
+            result = getErrorReportReceiver(pm, packageName, candidate);
+            if (result != null) {
+                return result;
+            }
+        }
+
+        // if there is a default receiver, try that
+        candidate = SystemProperties.get(DEFAULT_ERROR_RECEIVER_PROPERTY);
+        return getErrorReportReceiver(pm, packageName, candidate);
+    }
+
+    /**
+     * Return activity in receiverPackage that handles ACTION_APP_ERROR.
+     *
+     * @param pm PackageManager instance
+     * @param errorPackage package which caused the error
+     * @param receiverPackage candidate package to receive the error
+     * @return activity component within receiverPackage which handles
+     * ACTION_APP_ERROR, or null if not found
+     */
+    static ComponentName getErrorReportReceiver(PackageManager pm, String errorPackage,
+            String receiverPackage) {
+        if (receiverPackage == null || receiverPackage.length() == 0) {
+            return null;
+        }
+
+        // break the loop if it's the error report receiver package that crashed
+        if (receiverPackage.equals(errorPackage)) {
+            return null;
+        }
+
+        Intent intent = new Intent(Intent.ACTION_APP_ERROR);
+        intent.setPackage(receiverPackage);
+        ResolveInfo info = pm.resolveActivity(intent, 0);
+        if (info == null || info.activityInfo == null) {
+            return null;
+        }
+        return new ComponentName(receiverPackage, info.activityInfo.name);
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(type);
+        dest.writeString(packageName);
+        dest.writeString(installerPackageName);
+        dest.writeString(processName);
+        dest.writeLong(time);
+        dest.writeInt(systemApp ? 1 : 0);
+        dest.writeInt(crashInfo != null ? 1 : 0);
+
+        switch (type) {
+            case TYPE_CRASH:
+                if (crashInfo != null) {
+                    crashInfo.writeToParcel(dest, flags);
+                }
+                break;
+            case TYPE_ANR:
+                anrInfo.writeToParcel(dest, flags);
+                break;
+            case TYPE_BATTERY:
+                batteryInfo.writeToParcel(dest, flags);
+                break;
+            case TYPE_RUNNING_SERVICE:
+                runningServiceInfo.writeToParcel(dest, flags);
+                break;
+        }
+    }
+
+    public void readFromParcel(Parcel in) {
+        type = in.readInt();
+        packageName = in.readString();
+        installerPackageName = in.readString();
+        processName = in.readString();
+        time = in.readLong();
+        systemApp = in.readInt() == 1;
+        boolean hasCrashInfo = in.readInt() == 1;
+
+        switch (type) {
+            case TYPE_CRASH:
+                crashInfo = hasCrashInfo ? new CrashInfo(in) : null;
+                anrInfo = null;
+                batteryInfo = null;
+                runningServiceInfo = null;
+                break;
+            case TYPE_ANR:
+                anrInfo = new AnrInfo(in);
+                crashInfo = null;
+                batteryInfo = null;
+                runningServiceInfo = null;
+                break;
+            case TYPE_BATTERY:
+                batteryInfo = new BatteryInfo(in);
+                anrInfo = null;
+                crashInfo = null;
+                runningServiceInfo = null;
+                break;
+            case TYPE_RUNNING_SERVICE:
+                batteryInfo = null;
+                anrInfo = null;
+                crashInfo = null;
+                runningServiceInfo = new RunningServiceInfo(in);
+                break;
+        }
+    }
+
+    /**
+     * Describes an application crash.
+     */
+    public static class CrashInfo {
+        /**
+         * Class name of the exception that caused the crash.
+         */
+        public String exceptionClassName;
+
+        /**
+         * Message stored in the exception.
+         */
+        public String exceptionMessage;
+
+        /**
+         * File which the exception was thrown from.
+         */
+        public String throwFileName;
+
+        /**
+         * Class which the exception was thrown from.
+         */
+        public String throwClassName;
+
+        /**
+         * Method which the exception was thrown from.
+         */
+        public String throwMethodName;
+
+        /**
+         * Line number the exception was thrown from.
+         */
+        public int throwLineNumber;
+
+        /**
+         * Stack trace.
+         */
+        public String stackTrace;
+
+        /**
+         * Crash tag for some context.
+         * @hide
+         */
+        public String crashTag;
+
+        /**
+         * Create an uninitialized instance of CrashInfo.
+         */
+        public CrashInfo() {
+        }
+
+        /**
+         * Create an instance of CrashInfo initialized from an exception.
+         */
+        public CrashInfo(Throwable tr) {
+            StringWriter sw = new StringWriter();
+            PrintWriter pw = new FastPrintWriter(sw, false, 256);
+            tr.printStackTrace(pw);
+            pw.flush();
+            stackTrace = sanitizeString(sw.toString());
+            exceptionMessage = tr.getMessage();
+
+            // Populate fields with the "root cause" exception
+            Throwable rootTr = tr;
+            while (tr.getCause() != null) {
+                tr = tr.getCause();
+                if (tr.getStackTrace() != null && tr.getStackTrace().length > 0) {
+                    rootTr = tr;
+                }
+                String msg = tr.getMessage();
+                if (msg != null && msg.length() > 0) {
+                    exceptionMessage = msg;
+                }
+            }
+
+            exceptionClassName = rootTr.getClass().getName();
+            if (rootTr.getStackTrace().length > 0) {
+                StackTraceElement trace = rootTr.getStackTrace()[0];
+                throwFileName = trace.getFileName();
+                throwClassName = trace.getClassName();
+                throwMethodName = trace.getMethodName();
+                throwLineNumber = trace.getLineNumber();
+            } else {
+                throwFileName = "unknown";
+                throwClassName = "unknown";
+                throwMethodName = "unknown";
+                throwLineNumber = 0;
+            }
+
+            exceptionMessage = sanitizeString(exceptionMessage);
+        }
+
+        /** {@hide} */
+        public void appendStackTrace(String tr) {
+            stackTrace = sanitizeString(stackTrace + tr);
+        }
+
+        /**
+         * Ensure that the string is of reasonable size, truncating from the middle if needed.
+         */
+        private String sanitizeString(String s) {
+            int prefixLength = 10 * 1024;
+            int suffixLength = 10 * 1024;
+            int acceptableLength = prefixLength + suffixLength;
+
+            if (s != null && s.length() > acceptableLength) {
+                String replacement =
+                        "\n[TRUNCATED " + (s.length() - acceptableLength) + " CHARS]\n";
+
+                StringBuilder sb = new StringBuilder(acceptableLength + replacement.length());
+                sb.append(s.substring(0, prefixLength));
+                sb.append(replacement);
+                sb.append(s.substring(s.length() - suffixLength));
+                return sb.toString();
+            }
+            return s;
+        }
+
+        /**
+         * Create an instance of CrashInfo initialized from a Parcel.
+         */
+        public CrashInfo(Parcel in) {
+            exceptionClassName = in.readString();
+            exceptionMessage = in.readString();
+            throwFileName = in.readString();
+            throwClassName = in.readString();
+            throwMethodName = in.readString();
+            throwLineNumber = in.readInt();
+            stackTrace = in.readString();
+            crashTag = in.readString();
+        }
+
+        /**
+         * Save a CrashInfo instance to a parcel.
+         */
+        public void writeToParcel(Parcel dest, int flags) {
+            int start = dest.dataPosition();
+            dest.writeString(exceptionClassName);
+            dest.writeString(exceptionMessage);
+            dest.writeString(throwFileName);
+            dest.writeString(throwClassName);
+            dest.writeString(throwMethodName);
+            dest.writeInt(throwLineNumber);
+            dest.writeString(stackTrace);
+            dest.writeString(crashTag);
+            int total = dest.dataPosition()-start;
+            if (Binder.CHECK_PARCEL_SIZE && total > 20*1024) {
+                Slog.d("Error", "ERR: exClass=" + exceptionClassName);
+                Slog.d("Error", "ERR: exMsg=" + exceptionMessage);
+                Slog.d("Error", "ERR: file=" + throwFileName);
+                Slog.d("Error", "ERR: class=" + throwClassName);
+                Slog.d("Error", "ERR: method=" + throwMethodName + " line=" + throwLineNumber);
+                Slog.d("Error", "ERR: stack=" + stackTrace);
+                Slog.d("Error", "ERR: TOTAL BYTES WRITTEN: " + (dest.dataPosition()-start));
+            }
+        }
+
+        /**
+         * Dump a CrashInfo instance to a Printer.
+         */
+        public void dump(Printer pw, String prefix) {
+            pw.println(prefix + "exceptionClassName: " + exceptionClassName);
+            pw.println(prefix + "exceptionMessage: " + exceptionMessage);
+            pw.println(prefix + "throwFileName: " + throwFileName);
+            pw.println(prefix + "throwClassName: " + throwClassName);
+            pw.println(prefix + "throwMethodName: " + throwMethodName);
+            pw.println(prefix + "throwLineNumber: " + throwLineNumber);
+            pw.println(prefix + "stackTrace: " + stackTrace);
+        }
+    }
+
+    /**
+     * Parcelable version of {@link CrashInfo}
+     *
+     * @hide
+     */
+    public static class ParcelableCrashInfo extends CrashInfo implements Parcelable {
+        /**
+         * Create an uninitialized instance of CrashInfo.
+         */
+        public ParcelableCrashInfo() {
+        }
+
+        /**
+         * Create an instance of CrashInfo initialized from an exception.
+         */
+        public ParcelableCrashInfo(Throwable tr) {
+            super(tr);
+        }
+
+        public ParcelableCrashInfo(Parcel in) {
+            super(in);
+        }
+
+        public int describeContents() {
+            return 0;
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<ParcelableCrashInfo> CREATOR =
+                new Parcelable.Creator<ParcelableCrashInfo>() {
+                    @Override
+                    public ParcelableCrashInfo createFromParcel(Parcel in) {
+                        return new ParcelableCrashInfo(in);
+                    }
+
+                    @Override
+                    public ParcelableCrashInfo[] newArray(int size) {
+                        return new ParcelableCrashInfo[size];
+                    }
+                };
+    }
+
+    /**
+     * Describes an application not responding error.
+     */
+    public static class AnrInfo {
+        /**
+         * Activity name.
+         */
+        public String activity;
+
+        /**
+         * Description of the operation that timed out.
+         */
+        public String cause;
+
+        /**
+         * Additional info, including CPU stats.
+         */
+        public String info;
+
+        /**
+         * Create an uninitialized instance of AnrInfo.
+         */
+        public AnrInfo() {
+        }
+
+        /**
+         * Create an instance of AnrInfo initialized from a Parcel.
+         */
+        public AnrInfo(Parcel in) {
+            activity = in.readString();
+            cause = in.readString();
+            info = in.readString();
+        }
+
+        /**
+         * Save an AnrInfo instance to a parcel.
+         */
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeString(activity);
+            dest.writeString(cause);
+            dest.writeString(info);
+        }
+
+        /**
+         * Dump an AnrInfo instance to a Printer.
+         */
+        public void dump(Printer pw, String prefix) {
+            pw.println(prefix + "activity: " + activity);
+            pw.println(prefix + "cause: " + cause);
+            pw.println(prefix + "info: " + info);
+        }
+    }
+
+    /**
+     * Describes a battery usage report.
+     */
+    public static class BatteryInfo {
+        /**
+         * Percentage of the battery that was used up by the process.
+         */
+        public int usagePercent;
+
+        /**
+         * Duration in microseconds over which the process used the above
+         * percentage of battery.
+         */
+        public long durationMicros;
+
+        /**
+         * Dump of various info impacting battery use.
+         */
+        public String usageDetails;
+
+        /**
+         * Checkin details.
+         */
+        public String checkinDetails;
+
+        /**
+         * Create an uninitialized instance of BatteryInfo.
+         */
+        public BatteryInfo() {
+        }
+
+        /**
+         * Create an instance of BatteryInfo initialized from a Parcel.
+         */
+        public BatteryInfo(Parcel in) {
+            usagePercent = in.readInt();
+            durationMicros = in.readLong();
+            usageDetails = in.readString();
+            checkinDetails = in.readString();
+        }
+
+        /**
+         * Save a BatteryInfo instance to a parcel.
+         */
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(usagePercent);
+            dest.writeLong(durationMicros);
+            dest.writeString(usageDetails);
+            dest.writeString(checkinDetails);
+        }
+
+        /**
+         * Dump a BatteryInfo instance to a Printer.
+         */
+        public void dump(Printer pw, String prefix) {
+            pw.println(prefix + "usagePercent: " + usagePercent);
+            pw.println(prefix + "durationMicros: " + durationMicros);
+            pw.println(prefix + "usageDetails: " + usageDetails);
+            pw.println(prefix + "checkinDetails: " + checkinDetails);
+        }
+    }
+
+    /**
+     * Describes a running service report.
+     */
+    public static class RunningServiceInfo {
+        /**
+         * Duration in milliseconds that the service has been running.
+         */
+        public long durationMillis;
+
+        /**
+         * Dump of debug information about the service.
+         */
+        public String serviceDetails;
+
+        /**
+         * Create an uninitialized instance of RunningServiceInfo.
+         */
+        public RunningServiceInfo() {
+        }
+
+        /**
+         * Create an instance of RunningServiceInfo initialized from a Parcel.
+         */
+        public RunningServiceInfo(Parcel in) {
+            durationMillis = in.readLong();
+            serviceDetails = in.readString();
+        }
+
+        /**
+         * Save a RunningServiceInfo instance to a parcel.
+         */
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeLong(durationMillis);
+            dest.writeString(serviceDetails);
+        }
+
+        /**
+         * Dump a BatteryInfo instance to a Printer.
+         */
+        public void dump(Printer pw, String prefix) {
+            pw.println(prefix + "durationMillis: " + durationMillis);
+            pw.println(prefix + "serviceDetails: " + serviceDetails);
+        }
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<ApplicationErrorReport> CREATOR
+            = new Parcelable.Creator<ApplicationErrorReport>() {
+        public ApplicationErrorReport createFromParcel(Parcel source) {
+            return new ApplicationErrorReport(source);
+        }
+
+        public ApplicationErrorReport[] newArray(int size) {
+            return new ApplicationErrorReport[size];
+        }
+    };
+
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Dump the report to a Printer.
+     */
+    public void dump(Printer pw, String prefix) {
+        pw.println(prefix + "type: " + type);
+        pw.println(prefix + "packageName: " + packageName);
+        pw.println(prefix + "installerPackageName: " + installerPackageName);
+        pw.println(prefix + "processName: " + processName);
+        pw.println(prefix + "time: " + time);
+        pw.println(prefix + "systemApp: " + systemApp);
+
+        switch (type) {
+            case TYPE_CRASH:
+                crashInfo.dump(pw, prefix);
+                break;
+            case TYPE_ANR:
+                anrInfo.dump(pw, prefix);
+                break;
+            case TYPE_BATTERY:
+                batteryInfo.dump(pw, prefix);
+                break;
+            case TYPE_RUNNING_SERVICE:
+                runningServiceInfo.dump(pw, prefix);
+                break;
+        }
+    }
+}
diff --git a/android/app/ApplicationLoaders.java b/android/app/ApplicationLoaders.java
new file mode 100644
index 0000000..2e59b90
--- /dev/null
+++ b/android/app/ApplicationLoaders.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.UnsupportedAppUsage;
+import android.content.pm.SharedLibraryInfo;
+import android.os.Build;
+import android.os.GraphicsEnvironment;
+import android.os.Trace;
+import android.util.ArrayMap;
+import android.util.Log;
+
+import com.android.internal.os.ClassLoaderFactory;
+
+import dalvik.system.PathClassLoader;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** @hide */
+public class ApplicationLoaders {
+    private static final String TAG = "ApplicationLoaders";
+
+    @UnsupportedAppUsage
+    public static ApplicationLoaders getDefault() {
+        return gApplicationLoaders;
+    }
+
+    ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
+                               String librarySearchPath, String libraryPermittedPath,
+                               ClassLoader parent, String classLoaderName) {
+        return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled,
+                              librarySearchPath, libraryPermittedPath, parent, classLoaderName,
+                              null);
+    }
+
+    ClassLoader getClassLoaderWithSharedLibraries(
+            String zip, int targetSdkVersion, boolean isBundled,
+            String librarySearchPath, String libraryPermittedPath,
+            ClassLoader parent, String classLoaderName,
+            List<ClassLoader> sharedLibraries) {
+        // For normal usage the cache key used is the same as the zip path.
+        return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
+                              libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries);
+    }
+
+    /**
+     * Gets a class loader for a shared library. Additional dependent shared libraries are allowed
+     * to be specified (sharedLibraries).
+     *
+     * Additionally, as an optimization, this will return a pre-created ClassLoader if one has
+     * been cached by createAndCacheNonBootclasspathSystemClassLoaders.
+     */
+    ClassLoader getSharedLibraryClassLoaderWithSharedLibraries(String zip, int targetSdkVersion,
+            boolean isBundled, String librarySearchPath, String libraryPermittedPath,
+            ClassLoader parent, String classLoaderName, List<ClassLoader> sharedLibraries) {
+        ClassLoader loader = getCachedNonBootclasspathSystemLib(zip, parent, classLoaderName,
+                sharedLibraries);
+        if (loader != null) {
+            return loader;
+        }
+
+        return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled,
+              librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries);
+    }
+
+    private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
+                                       String librarySearchPath, String libraryPermittedPath,
+                                       ClassLoader parent, String cacheKey,
+                                       String classLoaderName, List<ClassLoader> sharedLibraries) {
+        /*
+         * This is the parent we use if they pass "null" in.  In theory
+         * this should be the "system" class loader; in practice we
+         * don't use that and can happily (and more efficiently) use the
+         * bootstrap class loader.
+         */
+        ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();
+
+        synchronized (mLoaders) {
+            if (parent == null) {
+                parent = baseParent;
+            }
+
+            /*
+             * If we're one step up from the base class loader, find
+             * something in our cache.  Otherwise, we create a whole
+             * new ClassLoader for the zip archive.
+             */
+            if (parent == baseParent) {
+                ClassLoader loader = mLoaders.get(cacheKey);
+                if (loader != null) {
+                    return loader;
+                }
+
+                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
+
+                ClassLoader classloader = ClassLoaderFactory.createClassLoader(
+                        zip,  librarySearchPath, libraryPermittedPath, parent,
+                        targetSdkVersion, isBundled, classLoaderName, sharedLibraries);
+
+                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+                Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setLayerPaths");
+                GraphicsEnvironment.getInstance().setLayerPaths(
+                        classloader, librarySearchPath, libraryPermittedPath);
+                Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+
+                if (cacheKey != null) {
+                    mLoaders.put(cacheKey, classloader);
+                }
+                return classloader;
+            }
+
+            Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
+            ClassLoader loader = ClassLoaderFactory.createClassLoader(
+                    zip, null, parent, classLoaderName, sharedLibraries);
+            Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
+            return loader;
+        }
+    }
+
+    /**
+     * Caches system library class loaders which are not on the bootclasspath but are still used
+     * by many system apps.
+     *
+     * All libraries in the closure of libraries to be loaded must be in libs. A library can
+     * only depend on libraries that come before it in the list.
+     */
+    public void createAndCacheNonBootclasspathSystemClassLoaders(SharedLibraryInfo[] libs) {
+        if (mSystemLibsCacheMap != null) {
+            throw new IllegalStateException("Already cached.");
+        }
+
+        mSystemLibsCacheMap = new HashMap<String, CachedClassLoader>();
+
+        for (SharedLibraryInfo lib : libs) {
+            createAndCacheNonBootclasspathSystemClassLoader(lib);
+        }
+    }
+
+    /**
+     * Caches a single non-bootclasspath class loader.
+     *
+     * All of this library's dependencies must have previously been cached. Otherwise, an exception
+     * is thrown.
+     */
+    private void createAndCacheNonBootclasspathSystemClassLoader(SharedLibraryInfo lib) {
+        String path = lib.getPath();
+        List<SharedLibraryInfo> dependencies = lib.getDependencies();
+
+        // get cached classloaders for dependencies
+        ArrayList<ClassLoader> sharedLibraries = null;
+        if (dependencies != null) {
+            sharedLibraries = new ArrayList<ClassLoader>(dependencies.size());
+            for (SharedLibraryInfo dependency : dependencies) {
+                String dependencyPath = dependency.getPath();
+                CachedClassLoader cached = mSystemLibsCacheMap.get(dependencyPath);
+
+                if (cached == null) {
+                    throw new IllegalStateException("Failed to find dependency " + dependencyPath
+                            + " of cachedlibrary " + path);
+                }
+
+                sharedLibraries.add(cached.loader);
+            }
+        }
+
+        // assume cached libraries work with current sdk since they are built-in
+        ClassLoader classLoader = getClassLoader(path, Build.VERSION.SDK_INT, true /*isBundled*/,
+                null /*librarySearchPath*/, null /*libraryPermittedPath*/, null /*parent*/,
+                null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/);
+
+        if (classLoader == null) {
+            // bad configuration or break in classloading code
+            throw new IllegalStateException("Failed to cache " + path);
+        }
+
+        CachedClassLoader cached = new CachedClassLoader();
+        cached.loader = classLoader;
+        cached.sharedLibraries = sharedLibraries;
+
+        Log.d(TAG, "Created zygote-cached class loader: " + path);
+        mSystemLibsCacheMap.put(path, cached);
+    }
+
+    private static boolean sharedLibrariesEquals(List<ClassLoader> lhs, List<ClassLoader> rhs) {
+        if (lhs == null) {
+            return rhs == null;
+        }
+
+        return lhs.equals(rhs);
+    }
+
+    /**
+     * Returns lib cached with createAndCacheNonBootclasspathSystemClassLoader. This is called by
+     * the zygote during caching.
+     *
+     * If there is an error or the cache is not available, this returns null.
+     */
+    public ClassLoader getCachedNonBootclasspathSystemLib(String zip, ClassLoader parent,
+            String classLoaderName, List<ClassLoader> sharedLibraries) {
+        if (mSystemLibsCacheMap == null) {
+            return null;
+        }
+
+        // we only cache top-level libs with the default class loader
+        if (parent != null || classLoaderName != null) {
+            return null;
+        }
+
+        CachedClassLoader cached = mSystemLibsCacheMap.get(zip);
+        if (cached == null) {
+            return null;
+        }
+
+        // cached must be built and loaded in the same environment
+        if (!sharedLibrariesEquals(sharedLibraries, cached.sharedLibraries)) {
+            Log.w(TAG, "Unexpected environment for cached library: (" + sharedLibraries + "|"
+                    + cached.sharedLibraries + ")");
+            return null;
+        }
+
+        Log.d(TAG, "Returning zygote-cached class loader: " + zip);
+        return cached.loader;
+    }
+
+    /**
+     * Creates a classloader for the WebView APK and places it in the cache of loaders maintained
+     * by this class. This is used in the WebView zygote, where its presence in the cache speeds up
+     * startup and enables memory sharing.
+     */
+    public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath,
+                                                        String cacheKey) {
+        // The correct paths are calculated by WebViewZygote in the system server and passed to
+        // us here. We hardcode the other parameters: WebView always targets the current SDK,
+        // does not need to use non-public system libraries, and uses the base classloader as its
+        // parent to permit usage of the cache.
+        // The cache key is passed separately to enable the stub WebView to be cached under the
+        // stub's APK path, when the actual package path is the donor APK.
+        return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null,
+                              cacheKey, null /* classLoaderName */, null /* sharedLibraries */);
+    }
+
+    /**
+     * Adds a new path the classpath of the given loader.
+     * @throws IllegalStateException if the provided class loader is not a {@link PathClassLoader}.
+     */
+    void addPath(ClassLoader classLoader, String dexPath) {
+        if (!(classLoader instanceof PathClassLoader)) {
+            throw new IllegalStateException("class loader is not a PathClassLoader");
+        }
+        final PathClassLoader baseDexClassLoader = (PathClassLoader) classLoader;
+        baseDexClassLoader.addDexPath(dexPath);
+    }
+
+    /**
+     * @hide
+     */
+    void addNative(ClassLoader classLoader, Collection<String> libPaths) {
+        if (!(classLoader instanceof PathClassLoader)) {
+            throw new IllegalStateException("class loader is not a PathClassLoader");
+        }
+        final PathClassLoader baseDexClassLoader = (PathClassLoader) classLoader;
+        baseDexClassLoader.addNativePath(libPaths);
+    }
+
+    @UnsupportedAppUsage
+    private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<>();
+
+    private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders();
+
+    private static class CachedClassLoader {
+        ClassLoader loader;
+
+        /**
+         * The shared libraries used when constructing loader for verification.
+         */
+        List<ClassLoader> sharedLibraries;
+    }
+
+    /**
+     * This is a map of zip to associated class loader.
+     */
+    private Map<String, CachedClassLoader> mSystemLibsCacheMap = null;
+}
diff --git a/android/app/ApplicationPackageManager.java b/android/app/ApplicationPackageManager.java
new file mode 100644
index 0000000..360be35
--- /dev/null
+++ b/android/app/ApplicationPackageManager.java
@@ -0,0 +1,3157 @@
+/*
+ * 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.app;
+
+import android.annotation.DrawableRes;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.annotation.UnsupportedAppUsage;
+import android.annotation.UserIdInt;
+import android.annotation.XmlRes;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.ChangedPackages;
+import android.content.pm.ComponentInfo;
+import android.content.pm.FeatureInfo;
+import android.content.pm.IOnPermissionsChangeListener;
+import android.content.pm.IPackageDataObserver;
+import android.content.pm.IPackageDeleteObserver;
+import android.content.pm.IPackageManager;
+import android.content.pm.IPackageMoveObserver;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.InstantAppInfo;
+import android.content.pm.InstrumentationInfo;
+import android.content.pm.IntentFilterVerificationInfo;
+import android.content.pm.KeySet;
+import android.content.pm.ModuleInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageInstaller;
+import android.content.pm.PackageItemInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.content.pm.PermissionGroupInfo;
+import android.content.pm.PermissionInfo;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.pm.SharedLibraryInfo;
+import android.content.pm.SuspendDialogInfo;
+import android.content.pm.VerifierDeviceIdentity;
+import android.content.pm.VersionedPackage;
+import android.content.pm.dex.ArtManager;
+import android.content.res.Resources;
+import android.content.res.XmlResourceParser;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.PersistableBundle;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.StrictMode;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageManager;
+import android.os.storage.VolumeInfo;
+import android.provider.Settings;
+import android.system.ErrnoException;
+import android.system.Os;
+import android.system.OsConstants;
+import android.system.StructStat;
+import android.text.TextUtils;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.IconDrawableFactory;
+import android.util.LauncherIcons;
+import android.util.Log;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
+import com.android.internal.util.UserIcons;
+
+import dalvik.system.VMRuntime;
+
+import libcore.util.EmptyArray;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+/** @hide */
+public class ApplicationPackageManager extends PackageManager {
+    private static final String TAG = "ApplicationPackageManager";
+    private final static boolean DEBUG_ICONS = false;
+
+    private static final int DEFAULT_EPHEMERAL_COOKIE_MAX_SIZE_BYTES = 16384; // 16KB
+
+    // Default flags to use with PackageManager when no flags are given.
+    private final static int sDefaultFlags = PackageManager.GET_SHARED_LIBRARY_FILES;
+
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private UserManager mUserManager;
+    @GuardedBy("mLock")
+    private PackageInstaller mInstaller;
+    @GuardedBy("mLock")
+    private ArtManager mArtManager;
+
+    @GuardedBy("mDelegates")
+    private final ArrayList<MoveCallbackDelegate> mDelegates = new ArrayList<>();
+
+    @GuardedBy("mLock")
+    private String mPermissionsControllerPackageName;
+
+    UserManager getUserManager() {
+        synchronized (mLock) {
+            if (mUserManager == null) {
+                mUserManager = UserManager.get(mContext);
+            }
+            return mUserManager;
+        }
+    }
+
+    @Override
+    public int getUserId() {
+        return mContext.getUserId();
+    }
+
+    @Override
+    public PackageInfo getPackageInfo(String packageName, int flags)
+            throws NameNotFoundException {
+        return getPackageInfoAsUser(packageName, flags, getUserId());
+    }
+
+    @Override
+    public PackageInfo getPackageInfo(VersionedPackage versionedPackage, int flags)
+            throws NameNotFoundException {
+        final int userId = getUserId();
+        try {
+            PackageInfo pi = mPM.getPackageInfoVersioned(versionedPackage,
+                    updateFlagsForPackage(flags, userId), userId);
+            if (pi != null) {
+                return pi;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        throw new NameNotFoundException(versionedPackage.toString());
+    }
+
+    @Override
+    public PackageInfo getPackageInfoAsUser(String packageName, int flags, int userId)
+            throws NameNotFoundException {
+        try {
+            PackageInfo pi = mPM.getPackageInfo(packageName,
+                    updateFlagsForPackage(flags, userId), userId);
+            if (pi != null) {
+                return pi;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        throw new NameNotFoundException(packageName);
+    }
+
+    @Override
+    public String[] currentToCanonicalPackageNames(String[] names) {
+        try {
+            return mPM.currentToCanonicalPackageNames(names);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public String[] canonicalToCurrentPackageNames(String[] names) {
+        try {
+            return mPM.canonicalToCurrentPackageNames(names);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public Intent getLaunchIntentForPackage(String packageName) {
+        // First see if the package has an INFO activity; the existence of
+        // such an activity is implied to be the desired front-door for the
+        // overall package (such as if it has multiple launcher entries).
+        Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
+        intentToResolve.addCategory(Intent.CATEGORY_INFO);
+        intentToResolve.setPackage(packageName);
+        List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0);
+
+        // Otherwise, try to find a main launcher activity.
+        if (ris == null || ris.size() <= 0) {
+            // reuse the intent instance
+            intentToResolve.removeCategory(Intent.CATEGORY_INFO);
+            intentToResolve.addCategory(Intent.CATEGORY_LAUNCHER);
+            intentToResolve.setPackage(packageName);
+            ris = queryIntentActivities(intentToResolve, 0);
+        }
+        if (ris == null || ris.size() <= 0) {
+            return null;
+        }
+        Intent intent = new Intent(intentToResolve);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClassName(ris.get(0).activityInfo.packageName,
+                ris.get(0).activityInfo.name);
+        return intent;
+    }
+
+    @Override
+    public Intent getLeanbackLaunchIntentForPackage(String packageName) {
+        return getLaunchIntentForPackageAndCategory(packageName, Intent.CATEGORY_LEANBACK_LAUNCHER);
+    }
+
+    @Override
+    public Intent getCarLaunchIntentForPackage(String packageName) {
+        return getLaunchIntentForPackageAndCategory(packageName, Intent.CATEGORY_CAR_LAUNCHER);
+    }
+
+    private Intent getLaunchIntentForPackageAndCategory(String packageName, String category) {
+        // Try to find a main launcher activity for the given categories.
+        Intent intentToResolve = new Intent(Intent.ACTION_MAIN);
+        intentToResolve.addCategory(category);
+        intentToResolve.setPackage(packageName);
+        List<ResolveInfo> ris = queryIntentActivities(intentToResolve, 0);
+
+        if (ris == null || ris.size() <= 0) {
+            return null;
+        }
+        Intent intent = new Intent(intentToResolve);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        intent.setClassName(ris.get(0).activityInfo.packageName,
+                ris.get(0).activityInfo.name);
+        return intent;
+    }
+
+    @Override
+    public int[] getPackageGids(String packageName) throws NameNotFoundException {
+        return getPackageGids(packageName, 0);
+    }
+
+    @Override
+    public int[] getPackageGids(String packageName, int flags)
+            throws NameNotFoundException {
+        final int userId = getUserId();
+        try {
+            int[] gids = mPM.getPackageGids(packageName,
+                    updateFlagsForPackage(flags, userId), userId);
+            if (gids != null) {
+                return gids;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(packageName);
+    }
+
+    @Override
+    public int getPackageUid(String packageName, int flags) throws NameNotFoundException {
+        return getPackageUidAsUser(packageName, flags, getUserId());
+    }
+
+    @Override
+    public int getPackageUidAsUser(String packageName, int userId) throws NameNotFoundException {
+        return getPackageUidAsUser(packageName, 0, userId);
+    }
+
+    @Override
+    public int getPackageUidAsUser(String packageName, int flags, int userId)
+            throws NameNotFoundException {
+        try {
+            int uid = mPM.getPackageUid(packageName,
+                    updateFlagsForPackage(flags, userId), userId);
+            if (uid >= 0) {
+                return uid;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(packageName);
+    }
+
+    @Override
+    public PermissionInfo getPermissionInfo(String name, int flags)
+            throws NameNotFoundException {
+        try {
+            PermissionInfo pi = mPM.getPermissionInfo(name,
+                    mContext.getOpPackageName(), flags);
+            if (pi != null) {
+                return pi;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(name);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<PermissionInfo> queryPermissionsByGroup(String group, int flags)
+            throws NameNotFoundException {
+        try {
+            ParceledListSlice<PermissionInfo> parceledList =
+                    mPM.queryPermissionsByGroup(group, flags);
+            if (parceledList != null) {
+                List<PermissionInfo> pi = parceledList.getList();
+                if (pi != null) {
+                    return pi;
+                }
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(group);
+    }
+
+    @Override
+    public boolean arePermissionsIndividuallyControlled() {
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_permissionsIndividuallyControlled);
+    }
+
+    @Override
+    public boolean isWirelessConsentModeEnabled() {
+        return mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_wirelessConsentRequired);
+    }
+
+    @Override
+    public PermissionGroupInfo getPermissionGroupInfo(String name,
+            int flags) throws NameNotFoundException {
+        try {
+            PermissionGroupInfo pgi = mPM.getPermissionGroupInfo(name, flags);
+            if (pgi != null) {
+                return pgi;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(name);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<PermissionGroupInfo> getAllPermissionGroups(int flags) {
+        try {
+            ParceledListSlice<PermissionGroupInfo> parceledList =
+                    mPM.getAllPermissionGroups(flags);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public ApplicationInfo getApplicationInfo(String packageName, int flags)
+            throws NameNotFoundException {
+        return getApplicationInfoAsUser(packageName, flags, getUserId());
+    }
+
+    @Override
+    public ApplicationInfo getApplicationInfoAsUser(String packageName, int flags, int userId)
+            throws NameNotFoundException {
+        try {
+            ApplicationInfo ai = mPM.getApplicationInfo(packageName,
+                    updateFlagsForApplication(flags, userId), userId);
+            if (ai != null) {
+                // This is a temporary hack. Callers must use
+                // createPackageContext(packageName).getApplicationInfo() to
+                // get the right paths.
+                return maybeAdjustApplicationInfo(ai);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(packageName);
+    }
+
+    private static ApplicationInfo maybeAdjustApplicationInfo(ApplicationInfo info) {
+        // If we're dealing with a multi-arch application that has both
+        // 32 and 64 bit shared libraries, we might need to choose the secondary
+        // depending on what the current runtime's instruction set is.
+        if (info.primaryCpuAbi != null && info.secondaryCpuAbi != null) {
+            final String runtimeIsa = VMRuntime.getRuntime().vmInstructionSet();
+
+            // Get the instruction set that the libraries of secondary Abi is supported.
+            // In presence of a native bridge this might be different than the one secondary Abi used.
+            String secondaryIsa = VMRuntime.getInstructionSet(info.secondaryCpuAbi);
+            final String secondaryDexCodeIsa = SystemProperties.get("ro.dalvik.vm.isa." + secondaryIsa);
+            secondaryIsa = secondaryDexCodeIsa.isEmpty() ? secondaryIsa : secondaryDexCodeIsa;
+
+            // If the runtimeIsa is the same as the primary isa, then we do nothing.
+            // Everything will be set up correctly because info.nativeLibraryDir will
+            // correspond to the right ISA.
+            if (runtimeIsa.equals(secondaryIsa)) {
+                ApplicationInfo modified = new ApplicationInfo(info);
+                modified.nativeLibraryDir = info.secondaryNativeLibraryDir;
+                return modified;
+            }
+        }
+        return info;
+    }
+
+    @Override
+    public ActivityInfo getActivityInfo(ComponentName className, int flags)
+            throws NameNotFoundException {
+        final int userId = getUserId();
+        try {
+            ActivityInfo ai = mPM.getActivityInfo(className,
+                    updateFlagsForComponent(flags, userId, null), userId);
+            if (ai != null) {
+                return ai;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(className.toString());
+    }
+
+    @Override
+    public ActivityInfo getReceiverInfo(ComponentName className, int flags)
+            throws NameNotFoundException {
+        final int userId = getUserId();
+        try {
+            ActivityInfo ai = mPM.getReceiverInfo(className,
+                    updateFlagsForComponent(flags, userId, null), userId);
+            if (ai != null) {
+                return ai;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(className.toString());
+    }
+
+    @Override
+    public ServiceInfo getServiceInfo(ComponentName className, int flags)
+            throws NameNotFoundException {
+        final int userId = getUserId();
+        try {
+            ServiceInfo si = mPM.getServiceInfo(className,
+                    updateFlagsForComponent(flags, userId, null), userId);
+            if (si != null) {
+                return si;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(className.toString());
+    }
+
+    @Override
+    public ProviderInfo getProviderInfo(ComponentName className, int flags)
+            throws NameNotFoundException {
+        final int userId = getUserId();
+        try {
+            ProviderInfo pi = mPM.getProviderInfo(className,
+                    updateFlagsForComponent(flags, userId, null), userId);
+            if (pi != null) {
+                return pi;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(className.toString());
+    }
+
+    @Override
+    public String[] getSystemSharedLibraryNames() {
+        try {
+            return mPM.getSystemSharedLibraryNames();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @Override
+    public @NonNull List<SharedLibraryInfo> getSharedLibraries(int flags) {
+        return getSharedLibrariesAsUser(flags, getUserId());
+    }
+
+    /** @hide */
+    @Override
+    @SuppressWarnings("unchecked")
+    public @NonNull List<SharedLibraryInfo> getSharedLibrariesAsUser(int flags, int userId) {
+        try {
+            ParceledListSlice<SharedLibraryInfo> sharedLibs = mPM.getSharedLibraries(
+                    mContext.getOpPackageName(), flags, userId);
+            if (sharedLibs == null) {
+                return Collections.emptyList();
+            }
+            return sharedLibs.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @NonNull
+    @Override
+    public List<SharedLibraryInfo> getDeclaredSharedLibraries(@NonNull String packageName,
+            @InstallFlags int flags) {
+        try {
+            ParceledListSlice<SharedLibraryInfo> sharedLibraries = mPM.getDeclaredSharedLibraries(
+                    packageName, flags, mContext.getUserId());
+            return sharedLibraries != null ? sharedLibraries.getList() : Collections.emptyList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @Override
+    public @NonNull String getServicesSystemSharedLibraryPackageName() {
+        try {
+            return mPM.getServicesSystemSharedLibraryPackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public @NonNull String getSharedSystemSharedLibraryPackageName() {
+        try {
+            return mPM.getSharedSystemSharedLibraryPackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public ChangedPackages getChangedPackages(int sequenceNumber) {
+        try {
+            return mPM.getChangedPackages(sequenceNumber, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public FeatureInfo[] getSystemAvailableFeatures() {
+        try {
+            ParceledListSlice<FeatureInfo> parceledList =
+                    mPM.getSystemAvailableFeatures();
+            if (parceledList == null) {
+                return new FeatureInfo[0];
+            }
+            final List<FeatureInfo> list = parceledList.getList();
+            final FeatureInfo[] res = new FeatureInfo[list.size()];
+            for (int i = 0; i < res.length; i++) {
+                res[i] = list.get(i);
+            }
+            return res;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean hasSystemFeature(String name) {
+        return hasSystemFeature(name, 0);
+    }
+
+    @Override
+    public boolean hasSystemFeature(String name, int version) {
+        try {
+            return mPM.hasSystemFeature(name, version);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int checkPermission(String permName, String pkgName) {
+        try {
+            return mPM.checkPermission(permName, pkgName, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean isPermissionRevokedByPolicy(String permName, String pkgName) {
+        try {
+            return mPM.isPermissionRevokedByPolicy(permName, pkgName, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public String getPermissionControllerPackageName() {
+        synchronized (mLock) {
+            if (mPermissionsControllerPackageName == null) {
+                try {
+                    mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            return mPermissionsControllerPackageName;
+        }
+    }
+
+    @Override
+    public boolean addPermission(PermissionInfo info) {
+        try {
+            return mPM.addPermission(info);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean addPermissionAsync(PermissionInfo info) {
+        try {
+            return mPM.addPermissionAsync(info);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void removePermission(String name) {
+        try {
+            mPM.removePermission(name);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void grantRuntimePermission(String packageName, String permissionName,
+            UserHandle user) {
+        try {
+            mPM.grantRuntimePermission(packageName, permissionName, user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void revokeRuntimePermission(String packageName, String permissionName,
+            UserHandle user) {
+        try {
+            mPM.revokeRuntimePermission(packageName, permissionName, user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int getPermissionFlags(String permissionName, String packageName, UserHandle user) {
+        try {
+            return mPM.getPermissionFlags(permissionName, packageName, user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void updatePermissionFlags(String permissionName, String packageName,
+            int flagMask, int flagValues, UserHandle user) {
+        try {
+            mPM.updatePermissionFlags(permissionName, packageName, flagMask,
+                    flagValues,
+                    mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.Q,
+                    user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public @NonNull Set<String> getWhitelistedRestrictedPermissions(
+            @NonNull String packageName, @PermissionWhitelistFlags int whitelistFlags) {
+        try {
+            final List<String> whitelist = mPM.getWhitelistedRestrictedPermissions(
+                    packageName, whitelistFlags, getUserId());
+            if (whitelist != null) {
+                return new ArraySet<>(whitelist);
+            }
+            return Collections.emptySet();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean addWhitelistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permission, @PermissionWhitelistFlags int whitelistFlags) {
+        try {
+            return mPM.addWhitelistedRestrictedPermission(packageName, permission,
+                    whitelistFlags, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean removeWhitelistedRestrictedPermission(@NonNull String packageName,
+            @NonNull String permission, @PermissionWhitelistFlags int whitelistFlags) {
+        try {
+            return mPM.removeWhitelistedRestrictedPermission(packageName, permission,
+                    whitelistFlags, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    @UnsupportedAppUsage
+    public boolean shouldShowRequestPermissionRationale(String permission) {
+        try {
+            return mPM.shouldShowRequestPermissionRationale(permission,
+                    mContext.getPackageName(), getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int checkSignatures(String pkg1, String pkg2) {
+        try {
+            return mPM.checkSignatures(pkg1, pkg2);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int checkSignatures(int uid1, int uid2) {
+        try {
+            return mPM.checkUidSignatures(uid1, uid2);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean hasSigningCertificate(
+            String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) {
+        try {
+            return mPM.hasSigningCertificate(packageName, certificate, type);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean hasSigningCertificate(
+            int uid, byte[] certificate, @PackageManager.CertificateInputType int type) {
+        try {
+            return mPM.hasUidSigningCertificate(uid, certificate, type);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public String[] getPackagesForUid(int uid) {
+        try {
+            return mPM.getPackagesForUid(uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public String getNameForUid(int uid) {
+        try {
+            return mPM.getNameForUid(uid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public String[] getNamesForUids(int[] uids) {
+        try {
+            return mPM.getNamesForUids(uids);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int getUidForSharedUser(String sharedUserName)
+            throws NameNotFoundException {
+        try {
+            int uid = mPM.getUidForSharedUser(sharedUserName);
+            if(uid != -1) {
+                return uid;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        throw new NameNotFoundException("No shared userid for user:"+sharedUserName);
+    }
+
+    @Override
+    public List<ModuleInfo> getInstalledModules(int flags) {
+        try {
+            return mPM.getInstalledModules(flags);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public ModuleInfo getModuleInfo(String packageName, int flags) throws NameNotFoundException {
+        try {
+            ModuleInfo mi = mPM.getModuleInfo(packageName, flags);
+            if (mi != null) {
+                return mi;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException("No module info for package: " + packageName);
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<PackageInfo> getInstalledPackages(int flags) {
+        return getInstalledPackagesAsUser(flags, getUserId());
+    }
+
+    /** @hide */
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<PackageInfo> getInstalledPackagesAsUser(int flags, int userId) {
+        try {
+            ParceledListSlice<PackageInfo> parceledList =
+                    mPM.getInstalledPackages(updateFlagsForPackage(flags, userId), userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<PackageInfo> getPackagesHoldingPermissions(
+            String[] permissions, int flags) {
+        final int userId = getUserId();
+        try {
+            ParceledListSlice<PackageInfo> parceledList =
+                    mPM.getPackagesHoldingPermissions(permissions,
+                            updateFlagsForPackage(flags, userId), userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<ApplicationInfo> getInstalledApplications(int flags) {
+        return getInstalledApplicationsAsUser(flags, getUserId());
+    }
+
+    /** @hide */
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<ApplicationInfo> getInstalledApplicationsAsUser(int flags, int userId) {
+        try {
+            ParceledListSlice<ApplicationInfo> parceledList =
+                    mPM.getInstalledApplications(updateFlagsForApplication(flags, userId), userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @SuppressWarnings("unchecked")
+    @Override
+    public List<InstantAppInfo> getInstantApps() {
+        try {
+            ParceledListSlice<InstantAppInfo> slice = mPM.getInstantApps(getUserId());
+            if (slice != null) {
+                return slice.getList();
+            }
+            return Collections.emptyList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @Override
+    public Drawable getInstantAppIcon(String packageName) {
+        try {
+            Bitmap bitmap = mPM.getInstantAppIcon(packageName, getUserId());
+            if (bitmap != null) {
+                return new BitmapDrawable(null, bitmap);
+            }
+            return null;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean isInstantApp() {
+        return isInstantApp(mContext.getPackageName());
+    }
+
+    @Override
+    public boolean isInstantApp(String packageName) {
+        try {
+            return mPM.isInstantApp(packageName, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    public int getInstantAppCookieMaxBytes() {
+        return Settings.Global.getInt(mContext.getContentResolver(),
+                Settings.Global.EPHEMERAL_COOKIE_MAX_SIZE_BYTES,
+                DEFAULT_EPHEMERAL_COOKIE_MAX_SIZE_BYTES);
+    }
+
+    @Override
+    public int getInstantAppCookieMaxSize() {
+        return getInstantAppCookieMaxBytes();
+    }
+
+    @Override
+    public @NonNull byte[] getInstantAppCookie() {
+        try {
+            final byte[] cookie = mPM.getInstantAppCookie(mContext.getPackageName(), getUserId());
+            if (cookie != null) {
+                return cookie;
+            } else {
+                return EmptyArray.BYTE;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void clearInstantAppCookie() {
+        updateInstantAppCookie(null);
+    }
+
+    @Override
+    public void updateInstantAppCookie(@NonNull byte[] cookie) {
+        if (cookie != null && cookie.length > getInstantAppCookieMaxBytes()) {
+            throw new IllegalArgumentException("instant cookie longer than "
+                    + getInstantAppCookieMaxBytes());
+        }
+        try {
+            mPM.setInstantAppCookie(mContext.getPackageName(), cookie, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @UnsupportedAppUsage
+    @Override
+    public boolean setInstantAppCookie(@NonNull byte[] cookie) {
+        try {
+            return mPM.setInstantAppCookie(mContext.getPackageName(), cookie, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public ResolveInfo resolveActivity(Intent intent, int flags) {
+        return resolveActivityAsUser(intent, flags, getUserId());
+    }
+
+    @Override
+    public ResolveInfo resolveActivityAsUser(Intent intent, int flags, int userId) {
+        try {
+            return mPM.resolveIntent(
+                intent,
+                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                updateFlagsForComponent(flags, userId, intent),
+                userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentActivities(Intent intent,
+                                                   int flags) {
+        return queryIntentActivitiesAsUser(intent, flags, getUserId());
+    }
+
+    /** @hide Same as above but for a specific user */
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<ResolveInfo> queryIntentActivitiesAsUser(Intent intent,
+            int flags, int userId) {
+        try {
+            ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentActivities(
+                    intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    updateFlagsForComponent(flags, userId, intent),
+                    userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<ResolveInfo> queryIntentActivityOptions(ComponentName caller, Intent[] specifics,
+            Intent intent, int flags) {
+        final int userId = getUserId();
+        final ContentResolver resolver = mContext.getContentResolver();
+
+        String[] specificTypes = null;
+        if (specifics != null) {
+            final int N = specifics.length;
+            for (int i=0; i<N; i++) {
+                Intent sp = specifics[i];
+                if (sp != null) {
+                    String t = sp.resolveTypeIfNeeded(resolver);
+                    if (t != null) {
+                        if (specificTypes == null) {
+                            specificTypes = new String[N];
+                        }
+                        specificTypes[i] = t;
+                    }
+                }
+            }
+        }
+
+        try {
+            ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentActivityOptions(
+                    caller,
+                    specifics,
+                    specificTypes,
+                    intent,
+                    intent.resolveTypeIfNeeded(resolver),
+                    updateFlagsForComponent(flags, userId, intent),
+                    userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<ResolveInfo> queryBroadcastReceiversAsUser(Intent intent, int flags, int userId) {
+        try {
+            ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentReceivers(
+                    intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    updateFlagsForComponent(flags, userId, intent),
+                    userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public List<ResolveInfo> queryBroadcastReceivers(Intent intent, int flags) {
+        return queryBroadcastReceiversAsUser(intent, flags, getUserId());
+    }
+
+    @Override
+    public ResolveInfo resolveServiceAsUser(Intent intent, @ResolveInfoFlags int flags,
+            @UserIdInt int userId) {
+        try {
+            return mPM.resolveService(
+                intent,
+                intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                updateFlagsForComponent(flags, userId, intent),
+                userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public ResolveInfo resolveService(Intent intent, int flags) {
+        return resolveServiceAsUser(intent, flags, getUserId());
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<ResolveInfo> queryIntentServicesAsUser(Intent intent, int flags, int userId) {
+        try {
+            ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentServices(
+                    intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    updateFlagsForComponent(flags, userId, intent),
+                    userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentServices(Intent intent, int flags) {
+        return queryIntentServicesAsUser(intent, flags, getUserId());
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<ResolveInfo> queryIntentContentProvidersAsUser(
+            Intent intent, int flags, int userId) {
+        try {
+            ParceledListSlice<ResolveInfo> parceledList = mPM.queryIntentContentProviders(
+                    intent,
+                    intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                    updateFlagsForComponent(flags, userId, intent),
+                    userId);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public List<ResolveInfo> queryIntentContentProviders(Intent intent, int flags) {
+        return queryIntentContentProvidersAsUser(intent, flags, getUserId());
+    }
+
+    @Override
+    public ProviderInfo resolveContentProvider(String name, int flags) {
+        return resolveContentProviderAsUser(name, flags, getUserId());
+    }
+
+    /** @hide **/
+    @Override
+    public ProviderInfo resolveContentProviderAsUser(String name, int flags, int userId) {
+        try {
+            return mPM.resolveContentProvider(name,
+                    updateFlagsForComponent(flags, userId, null), userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public List<ProviderInfo> queryContentProviders(String processName,
+            int uid, int flags) {
+        return queryContentProviders(processName, uid, flags, null);
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<ProviderInfo> queryContentProviders(String processName,
+            int uid, int flags, String metaDataKey) {
+        try {
+            ParceledListSlice<ProviderInfo> slice = mPM.queryContentProviders(processName, uid,
+                    updateFlagsForComponent(flags, UserHandle.getUserId(uid), null), metaDataKey);
+            return slice != null ? slice.getList() : Collections.<ProviderInfo>emptyList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public InstrumentationInfo getInstrumentationInfo(
+        ComponentName className, int flags)
+            throws NameNotFoundException {
+        try {
+            InstrumentationInfo ii = mPM.getInstrumentationInfo(
+                className, flags);
+            if (ii != null) {
+                return ii;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        throw new NameNotFoundException(className.toString());
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<InstrumentationInfo> queryInstrumentation(
+        String targetPackage, int flags) {
+        try {
+            ParceledListSlice<InstrumentationInfo> parceledList =
+                    mPM.queryInstrumentation(targetPackage, flags);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Nullable
+    @Override
+    public Drawable getDrawable(String packageName, @DrawableRes int resId,
+            @Nullable ApplicationInfo appInfo) {
+        final ResourceName name = new ResourceName(packageName, resId);
+        final Drawable cachedIcon = getCachedIcon(name);
+        if (cachedIcon != null) {
+            return cachedIcon;
+        }
+
+        if (appInfo == null) {
+            try {
+                appInfo = getApplicationInfo(packageName, sDefaultFlags);
+            } catch (NameNotFoundException e) {
+                return null;
+            }
+        }
+
+        if (resId != 0) {
+            try {
+                final Resources r = getResourcesForApplication(appInfo);
+                final Drawable dr = r.getDrawable(resId, null);
+                if (dr != null) {
+                    putCachedIcon(name, dr);
+                }
+
+                if (false) {
+                    RuntimeException e = new RuntimeException("here");
+                    e.fillInStackTrace();
+                    Log.w(TAG, "Getting drawable 0x" + Integer.toHexString(resId)
+                                    + " from package " + packageName
+                                    + ": app scale=" + r.getCompatibilityInfo().applicationScale
+                                    + ", caller scale=" + mContext.getResources()
+                                    .getCompatibilityInfo().applicationScale,
+                            e);
+                }
+                if (DEBUG_ICONS) {
+                    Log.v(TAG, "Getting drawable 0x"
+                            + Integer.toHexString(resId) + " from " + r
+                            + ": " + dr);
+                }
+                return dr;
+            } catch (NameNotFoundException e) {
+                Log.w("PackageManager", "Failure retrieving resources for "
+                        + appInfo.packageName);
+            } catch (Resources.NotFoundException e) {
+                Log.w("PackageManager", "Failure retrieving resources for "
+                        + appInfo.packageName + ": " + e.getMessage());
+            } catch (Exception e) {
+                // If an exception was thrown, fall through to return
+                // default icon.
+                Log.w("PackageManager", "Failure retrieving icon 0x"
+                        + Integer.toHexString(resId) + " in package "
+                        + packageName, e);
+            }
+        }
+
+        return null;
+    }
+
+    @Override public Drawable getActivityIcon(ComponentName activityName)
+            throws NameNotFoundException {
+        return getActivityInfo(activityName, sDefaultFlags).loadIcon(this);
+    }
+
+    @Override public Drawable getActivityIcon(Intent intent)
+            throws NameNotFoundException {
+        if (intent.getComponent() != null) {
+            return getActivityIcon(intent.getComponent());
+        }
+
+        ResolveInfo info = resolveActivity(
+            intent, PackageManager.MATCH_DEFAULT_ONLY);
+        if (info != null) {
+            return info.activityInfo.loadIcon(this);
+        }
+
+        throw new NameNotFoundException(intent.toUri(0));
+    }
+
+    @Override public Drawable getDefaultActivityIcon() {
+        return mContext.getDrawable(com.android.internal.R.drawable.sym_def_app_icon);
+    }
+
+    @Override public Drawable getApplicationIcon(ApplicationInfo info) {
+        return info.loadIcon(this);
+    }
+
+    @Override public Drawable getApplicationIcon(String packageName)
+            throws NameNotFoundException {
+        return getApplicationIcon(getApplicationInfo(packageName, sDefaultFlags));
+    }
+
+    @Override
+    public Drawable getActivityBanner(ComponentName activityName)
+            throws NameNotFoundException {
+        return getActivityInfo(activityName, sDefaultFlags).loadBanner(this);
+    }
+
+    @Override
+    public Drawable getActivityBanner(Intent intent)
+            throws NameNotFoundException {
+        if (intent.getComponent() != null) {
+            return getActivityBanner(intent.getComponent());
+        }
+
+        ResolveInfo info = resolveActivity(
+                intent, PackageManager.MATCH_DEFAULT_ONLY);
+        if (info != null) {
+            return info.activityInfo.loadBanner(this);
+        }
+
+        throw new NameNotFoundException(intent.toUri(0));
+    }
+
+    @Override
+    public Drawable getApplicationBanner(ApplicationInfo info) {
+        return info.loadBanner(this);
+    }
+
+    @Override
+    public Drawable getApplicationBanner(String packageName)
+            throws NameNotFoundException {
+        return getApplicationBanner(getApplicationInfo(packageName, sDefaultFlags));
+    }
+
+    @Override
+    public Drawable getActivityLogo(ComponentName activityName)
+            throws NameNotFoundException {
+        return getActivityInfo(activityName, sDefaultFlags).loadLogo(this);
+    }
+
+    @Override
+    public Drawable getActivityLogo(Intent intent)
+            throws NameNotFoundException {
+        if (intent.getComponent() != null) {
+            return getActivityLogo(intent.getComponent());
+        }
+
+        ResolveInfo info = resolveActivity(
+            intent, PackageManager.MATCH_DEFAULT_ONLY);
+        if (info != null) {
+            return info.activityInfo.loadLogo(this);
+        }
+
+        throw new NameNotFoundException(intent.toUri(0));
+    }
+
+    @Override
+    public Drawable getApplicationLogo(ApplicationInfo info) {
+        return info.loadLogo(this);
+    }
+
+    @Override
+    public Drawable getApplicationLogo(String packageName)
+            throws NameNotFoundException {
+        return getApplicationLogo(getApplicationInfo(packageName, sDefaultFlags));
+    }
+
+    @Override
+    public Drawable getUserBadgedIcon(Drawable icon, UserHandle user) {
+        if (!isManagedProfile(user.getIdentifier())) {
+            return icon;
+        }
+        Drawable badge = new LauncherIcons(mContext).getBadgeDrawable(
+                com.android.internal.R.drawable.ic_corp_icon_badge_case,
+                getUserBadgeColor(user));
+        return getBadgedDrawable(icon, badge, null, true);
+    }
+
+    @Override
+    public Drawable getUserBadgedDrawableForDensity(Drawable drawable, UserHandle user,
+            Rect badgeLocation, int badgeDensity) {
+        Drawable badgeDrawable = getUserBadgeForDensity(user, badgeDensity);
+        if (badgeDrawable == null) {
+            return drawable;
+        }
+        return getBadgedDrawable(drawable, badgeDrawable, badgeLocation, true);
+    }
+
+    @VisibleForTesting
+    public static final int[] CORP_BADGE_LABEL_RES_ID = new int[] {
+        com.android.internal.R.string.managed_profile_label_badge,
+        com.android.internal.R.string.managed_profile_label_badge_2,
+        com.android.internal.R.string.managed_profile_label_badge_3
+    };
+
+    private int getUserBadgeColor(UserHandle user) {
+        return IconDrawableFactory.getUserBadgeColor(getUserManager(), user.getIdentifier());
+    }
+
+    @Override
+    public Drawable getUserBadgeForDensity(UserHandle user, int density) {
+        Drawable badgeColor = getManagedProfileIconForDensity(user,
+                com.android.internal.R.drawable.ic_corp_badge_color, density);
+        if (badgeColor == null) {
+            return null;
+        }
+        Drawable badgeForeground = getDrawableForDensity(
+                com.android.internal.R.drawable.ic_corp_badge_case, density);
+        badgeForeground.setTint(getUserBadgeColor(user));
+        Drawable badge = new LayerDrawable(new Drawable[] {badgeColor, badgeForeground });
+        return badge;
+    }
+
+    @Override
+    public Drawable getUserBadgeForDensityNoBackground(UserHandle user, int density) {
+        Drawable badge = getManagedProfileIconForDensity(user,
+                com.android.internal.R.drawable.ic_corp_badge_no_background, density);
+        if (badge != null) {
+            badge.setTint(getUserBadgeColor(user));
+        }
+        return badge;
+    }
+
+    private Drawable getDrawableForDensity(int drawableId, int density) {
+        if (density <= 0) {
+            density = mContext.getResources().getDisplayMetrics().densityDpi;
+        }
+        return mContext.getResources().getDrawableForDensity(drawableId, density);
+    }
+
+    private Drawable getManagedProfileIconForDensity(UserHandle user, int drawableId, int density) {
+        if (isManagedProfile(user.getIdentifier())) {
+            return getDrawableForDensity(drawableId, density);
+        }
+        return null;
+    }
+
+    @Override
+    public CharSequence getUserBadgedLabel(CharSequence label, UserHandle user) {
+        if (isManagedProfile(user.getIdentifier())) {
+            int badge = getUserManager().getManagedProfileBadge(user.getIdentifier());
+            int resourceId = CORP_BADGE_LABEL_RES_ID[badge % CORP_BADGE_LABEL_RES_ID.length];
+            return Resources.getSystem().getString(resourceId, label);
+        }
+        return label;
+    }
+
+    @Override
+    public Resources getResourcesForActivity(ComponentName activityName)
+            throws NameNotFoundException {
+        return getResourcesForApplication(
+            getActivityInfo(activityName, sDefaultFlags).applicationInfo);
+    }
+
+    @Override
+    public Resources getResourcesForApplication(@NonNull ApplicationInfo app)
+            throws NameNotFoundException {
+        if (app.packageName.equals("system")) {
+            return mContext.mMainThread.getSystemUiContext().getResources();
+        }
+        final boolean sameUid = (app.uid == Process.myUid());
+        final Resources r = mContext.mMainThread.getTopLevelResources(
+                    sameUid ? app.sourceDir : app.publicSourceDir,
+                    sameUid ? app.splitSourceDirs : app.splitPublicSourceDirs,
+                    app.resourceDirs, app.sharedLibraryFiles, Display.DEFAULT_DISPLAY,
+                    mContext.mPackageInfo);
+        if (r != null) {
+            return r;
+        }
+        throw new NameNotFoundException("Unable to open " + app.publicSourceDir);
+
+    }
+
+    @Override
+    public Resources getResourcesForApplication(String appPackageName)
+            throws NameNotFoundException {
+        return getResourcesForApplication(
+            getApplicationInfo(appPackageName, sDefaultFlags));
+    }
+
+    /** @hide */
+    @Override
+    public Resources getResourcesForApplicationAsUser(String appPackageName, int userId)
+            throws NameNotFoundException {
+        if (userId < 0) {
+            throw new IllegalArgumentException(
+                    "Call does not support special user #" + userId);
+        }
+        if ("system".equals(appPackageName)) {
+            return mContext.mMainThread.getSystemUiContext().getResources();
+        }
+        try {
+            ApplicationInfo ai = mPM.getApplicationInfo(appPackageName, sDefaultFlags, userId);
+            if (ai != null) {
+                return getResourcesForApplication(ai);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        throw new NameNotFoundException("Package " + appPackageName + " doesn't exist");
+    }
+
+    volatile int mCachedSafeMode = -1;
+
+    @Override
+    public boolean isSafeMode() {
+        try {
+            if (mCachedSafeMode < 0) {
+                mCachedSafeMode = mPM.isSafeMode() ? 1 : 0;
+            }
+            return mCachedSafeMode != 0;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void addOnPermissionsChangeListener(OnPermissionsChangedListener listener) {
+        synchronized (mPermissionListeners) {
+            if (mPermissionListeners.get(listener) != null) {
+                return;
+            }
+            OnPermissionsChangeListenerDelegate delegate =
+                    new OnPermissionsChangeListenerDelegate(listener, Looper.getMainLooper());
+            try {
+                mPM.addOnPermissionsChangeListener(delegate);
+                mPermissionListeners.put(listener, delegate);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+    }
+
+    @Override
+    public void removeOnPermissionsChangeListener(OnPermissionsChangedListener listener) {
+        synchronized (mPermissionListeners) {
+            IOnPermissionsChangeListener delegate = mPermissionListeners.get(listener);
+            if (delegate != null) {
+                try {
+                    mPM.removeOnPermissionsChangeListener(delegate);
+                    mPermissionListeners.remove(listener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+        }
+    }
+
+    @UnsupportedAppUsage
+    static void configurationChanged() {
+        synchronized (sSync) {
+            sIconCache.clear();
+            sStringCache.clear();
+        }
+    }
+
+    @UnsupportedAppUsage
+    protected ApplicationPackageManager(ContextImpl context,
+                              IPackageManager pm) {
+        mContext = context;
+        mPM = pm;
+    }
+
+    /**
+     * Update given flags when being used to request {@link PackageInfo}.
+     */
+    private int updateFlagsForPackage(int flags, int userId) {
+        if ((flags & (GET_ACTIVITIES | GET_RECEIVERS | GET_SERVICES | GET_PROVIDERS)) != 0) {
+            // Caller is asking for component details, so they'd better be
+            // asking for specific Direct Boot matching behavior
+            if ((flags & (MATCH_DIRECT_BOOT_UNAWARE
+                    | MATCH_DIRECT_BOOT_AWARE
+                    | MATCH_DIRECT_BOOT_AUTO)) == 0) {
+                onImplicitDirectBoot(userId);
+            }
+        }
+        return flags;
+    }
+
+    /**
+     * Update given flags when being used to request {@link ApplicationInfo}.
+     */
+    private int updateFlagsForApplication(int flags, int userId) {
+        return updateFlagsForPackage(flags, userId);
+    }
+
+    /**
+     * Update given flags when being used to request {@link ComponentInfo}.
+     */
+    private int updateFlagsForComponent(int flags, int userId, Intent intent) {
+        if (intent != null) {
+            if ((intent.getFlags() & Intent.FLAG_DIRECT_BOOT_AUTO) != 0) {
+                flags |= MATCH_DIRECT_BOOT_AUTO;
+            }
+        }
+
+        // Caller is asking for component details, so they'd better be
+        // asking for specific Direct Boot matching behavior
+        if ((flags & (MATCH_DIRECT_BOOT_UNAWARE
+                | MATCH_DIRECT_BOOT_AWARE
+                | MATCH_DIRECT_BOOT_AUTO)) == 0) {
+            onImplicitDirectBoot(userId);
+        }
+        return flags;
+    }
+
+    private void onImplicitDirectBoot(int userId) {
+        // Only report if someone is relying on implicit behavior while the user
+        // is locked; code running when unlocked is going to see both aware and
+        // unaware components.
+        if (StrictMode.vmImplicitDirectBootEnabled()) {
+            // We can cache the unlocked state for the userId we're running as,
+            // since any relocking of that user will always result in our
+            // process being killed to release any CE FDs we're holding onto.
+            if (userId == UserHandle.myUserId()) {
+                if (mUserUnlocked) {
+                    return;
+                } else if (mContext.getSystemService(UserManager.class)
+                        .isUserUnlockingOrUnlocked(userId)) {
+                    mUserUnlocked = true;
+                } else {
+                    StrictMode.onImplicitDirectBoot();
+                }
+            } else if (!mContext.getSystemService(UserManager.class)
+                    .isUserUnlockingOrUnlocked(userId)) {
+                StrictMode.onImplicitDirectBoot();
+            }
+        }
+    }
+
+    @Nullable
+    private Drawable getCachedIcon(@NonNull ResourceName name) {
+        synchronized (sSync) {
+            final WeakReference<Drawable.ConstantState> wr = sIconCache.get(name);
+            if (DEBUG_ICONS) Log.v(TAG, "Get cached weak drawable ref for "
+                                   + name + ": " + wr);
+            if (wr != null) {   // we have the activity
+                final Drawable.ConstantState state = wr.get();
+                if (state != null) {
+                    if (DEBUG_ICONS) {
+                        Log.v(TAG, "Get cached drawable state for " + name + ": " + state);
+                    }
+                    // Note: It's okay here to not use the newDrawable(Resources) variant
+                    //       of the API. The ConstantState comes from a drawable that was
+                    //       originally created by passing the proper app Resources instance
+                    //       which means the state should already contain the proper
+                    //       resources specific information (like density.) See
+                    //       BitmapDrawable.BitmapState for instance.
+                    return state.newDrawable();
+                }
+                // our entry has been purged
+                sIconCache.remove(name);
+            }
+        }
+        return null;
+    }
+
+    private void putCachedIcon(@NonNull ResourceName name, @NonNull Drawable dr) {
+        synchronized (sSync) {
+            sIconCache.put(name, new WeakReference<>(dr.getConstantState()));
+            if (DEBUG_ICONS) Log.v(TAG, "Added cached drawable state for " + name + ": " + dr);
+        }
+    }
+
+    static void handlePackageBroadcast(int cmd, String[] pkgList, boolean hasPkgInfo) {
+        boolean immediateGc = false;
+        if (cmd == ApplicationThreadConstants.EXTERNAL_STORAGE_UNAVAILABLE) {
+            immediateGc = true;
+        }
+        if (pkgList != null && (pkgList.length > 0)) {
+            boolean needCleanup = false;
+            for (String ssp : pkgList) {
+                synchronized (sSync) {
+                    for (int i=sIconCache.size()-1; i>=0; i--) {
+                        ResourceName nm = sIconCache.keyAt(i);
+                        if (nm.packageName.equals(ssp)) {
+                            //Log.i(TAG, "Removing cached drawable for " + nm);
+                            sIconCache.removeAt(i);
+                            needCleanup = true;
+                        }
+                    }
+                    for (int i=sStringCache.size()-1; i>=0; i--) {
+                        ResourceName nm = sStringCache.keyAt(i);
+                        if (nm.packageName.equals(ssp)) {
+                            //Log.i(TAG, "Removing cached string for " + nm);
+                            sStringCache.removeAt(i);
+                            needCleanup = true;
+                        }
+                    }
+                }
+            }
+            if (needCleanup || hasPkgInfo) {
+                if (immediateGc) {
+                    // Schedule an immediate gc.
+                    Runtime.getRuntime().gc();
+                } else {
+                    ActivityThread.currentActivityThread().scheduleGcIdler();
+                }
+            }
+        }
+    }
+
+    private static final class ResourceName {
+        final String packageName;
+        final int iconId;
+
+        ResourceName(String _packageName, int _iconId) {
+            packageName = _packageName;
+            iconId = _iconId;
+        }
+
+        ResourceName(ApplicationInfo aInfo, int _iconId) {
+            this(aInfo.packageName, _iconId);
+        }
+
+        ResourceName(ComponentInfo cInfo, int _iconId) {
+            this(cInfo.applicationInfo.packageName, _iconId);
+        }
+
+        ResourceName(ResolveInfo rInfo, int _iconId) {
+            this(rInfo.activityInfo.applicationInfo.packageName, _iconId);
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            ResourceName that = (ResourceName) o;
+
+            if (iconId != that.iconId) return false;
+            return !(packageName != null ?
+                     !packageName.equals(that.packageName) : that.packageName != null);
+
+        }
+
+        @Override
+        public int hashCode() {
+            int result;
+            result = packageName.hashCode();
+            result = 31 * result + iconId;
+            return result;
+        }
+
+        @Override
+        public String toString() {
+            return "{ResourceName " + packageName + " / " + iconId + "}";
+        }
+    }
+
+    private CharSequence getCachedString(ResourceName name) {
+        synchronized (sSync) {
+            WeakReference<CharSequence> wr = sStringCache.get(name);
+            if (wr != null) {   // we have the activity
+                CharSequence cs = wr.get();
+                if (cs != null) {
+                    return cs;
+                }
+                // our entry has been purged
+                sStringCache.remove(name);
+            }
+        }
+        return null;
+    }
+
+    private void putCachedString(ResourceName name, CharSequence cs) {
+        synchronized (sSync) {
+            sStringCache.put(name, new WeakReference<CharSequence>(cs));
+        }
+    }
+
+    @Override
+    public CharSequence getText(String packageName, @StringRes int resid,
+                                ApplicationInfo appInfo) {
+        ResourceName name = new ResourceName(packageName, resid);
+        CharSequence text = getCachedString(name);
+        if (text != null) {
+            return text;
+        }
+        if (appInfo == null) {
+            try {
+                appInfo = getApplicationInfo(packageName, sDefaultFlags);
+            } catch (NameNotFoundException e) {
+                return null;
+            }
+        }
+        try {
+            Resources r = getResourcesForApplication(appInfo);
+            text = r.getText(resid);
+            putCachedString(name, text);
+            return text;
+        } catch (NameNotFoundException e) {
+            Log.w("PackageManager", "Failure retrieving resources for "
+                  + appInfo.packageName);
+        } catch (RuntimeException e) {
+            // If an exception was thrown, fall through to return
+            // default icon.
+            Log.w("PackageManager", "Failure retrieving text 0x"
+                  + Integer.toHexString(resid) + " in package "
+                  + packageName, e);
+        }
+        return null;
+    }
+
+    @Override
+    public XmlResourceParser getXml(String packageName, @XmlRes int resid,
+                                    ApplicationInfo appInfo) {
+        if (appInfo == null) {
+            try {
+                appInfo = getApplicationInfo(packageName, sDefaultFlags);
+            } catch (NameNotFoundException e) {
+                return null;
+            }
+        }
+        try {
+            Resources r = getResourcesForApplication(appInfo);
+            return r.getXml(resid);
+        } catch (RuntimeException e) {
+            // If an exception was thrown, fall through to return
+            // default icon.
+            Log.w("PackageManager", "Failure retrieving xml 0x"
+                  + Integer.toHexString(resid) + " in package "
+                  + packageName, e);
+        } catch (NameNotFoundException e) {
+            Log.w("PackageManager", "Failure retrieving resources for "
+                  + appInfo.packageName);
+        }
+        return null;
+    }
+
+    @Override
+    public CharSequence getApplicationLabel(ApplicationInfo info) {
+        return info.loadLabel(this);
+    }
+
+    @Override
+    public int installExistingPackage(String packageName) throws NameNotFoundException {
+        return installExistingPackage(packageName, PackageManager.INSTALL_REASON_UNKNOWN);
+    }
+
+    @Override
+    public int installExistingPackage(String packageName, int installReason)
+            throws NameNotFoundException {
+        return installExistingPackageAsUser(packageName, installReason, getUserId());
+    }
+
+    @Override
+    public int installExistingPackageAsUser(String packageName, int userId)
+            throws NameNotFoundException {
+        return installExistingPackageAsUser(packageName, PackageManager.INSTALL_REASON_UNKNOWN,
+                userId);
+    }
+
+    private int installExistingPackageAsUser(String packageName, int installReason, int userId)
+            throws NameNotFoundException {
+        try {
+            int res = mPM.installExistingPackageAsUser(packageName, userId,
+                    INSTALL_ALL_WHITELIST_RESTRICTED_PERMISSIONS, installReason, null);
+            if (res == INSTALL_FAILED_INVALID_URI) {
+                throw new NameNotFoundException("Package " + packageName + " doesn't exist");
+            }
+            return res;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void verifyPendingInstall(int id, int response) {
+        try {
+            mPM.verifyPendingInstall(id, response);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void extendVerificationTimeout(int id, int verificationCodeAtTimeout,
+            long millisecondsToDelay) {
+        try {
+            mPM.extendVerificationTimeout(id, verificationCodeAtTimeout, millisecondsToDelay);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void verifyIntentFilter(int id, int verificationCode, List<String> failedDomains) {
+        try {
+            mPM.verifyIntentFilter(id, verificationCode, failedDomains);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int getIntentVerificationStatusAsUser(String packageName, int userId) {
+        try {
+            return mPM.getIntentVerificationStatus(packageName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean updateIntentVerificationStatusAsUser(String packageName, int status, int userId) {
+        try {
+            return mPM.updateIntentVerificationStatus(packageName, status, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<IntentFilterVerificationInfo> getIntentFilterVerifications(String packageName) {
+        try {
+            ParceledListSlice<IntentFilterVerificationInfo> parceledList =
+                    mPM.getIntentFilterVerifications(packageName);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<IntentFilter> getAllIntentFilters(String packageName) {
+        try {
+            ParceledListSlice<IntentFilter> parceledList =
+                    mPM.getAllIntentFilters(packageName);
+            if (parceledList == null) {
+                return Collections.emptyList();
+            }
+            return parceledList.getList();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public String getDefaultBrowserPackageNameAsUser(int userId) {
+        try {
+            return mPM.getDefaultBrowserPackageName(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean setDefaultBrowserPackageNameAsUser(String packageName, int userId) {
+        try {
+            return mPM.setDefaultBrowserPackageName(packageName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void setInstallerPackageName(String targetPackage,
+            String installerPackageName) {
+        try {
+            mPM.setInstallerPackageName(targetPackage, installerPackageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void setUpdateAvailable(String packageName, boolean updateAvailable) {
+        try {
+            mPM.setUpdateAvailable(packageName, updateAvailable);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public String getInstallerPackageName(String packageName) {
+        try {
+            return mPM.getInstallerPackageName(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int getMoveStatus(int moveId) {
+        try {
+            return mPM.getMoveStatus(moveId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void registerMoveCallback(MoveCallback callback, Handler handler) {
+        synchronized (mDelegates) {
+            final MoveCallbackDelegate delegate = new MoveCallbackDelegate(callback,
+                    handler.getLooper());
+            try {
+                mPM.registerMoveCallback(delegate);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+            mDelegates.add(delegate);
+        }
+    }
+
+    @Override
+    public void unregisterMoveCallback(MoveCallback callback) {
+        synchronized (mDelegates) {
+            for (Iterator<MoveCallbackDelegate> i = mDelegates.iterator(); i.hasNext();) {
+                final MoveCallbackDelegate delegate = i.next();
+                if (delegate.mCallback == callback) {
+                    try {
+                        mPM.unregisterMoveCallback(delegate);
+                    } catch (RemoteException e) {
+                        throw e.rethrowFromSystemServer();
+                    }
+                    i.remove();
+                }
+            }
+        }
+    }
+
+    @Override
+    public int movePackage(String packageName, VolumeInfo vol) {
+        try {
+            final String volumeUuid;
+            if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) {
+                volumeUuid = StorageManager.UUID_PRIVATE_INTERNAL;
+            } else if (vol.isPrimaryPhysical()) {
+                volumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL;
+            } else {
+                volumeUuid = Preconditions.checkNotNull(vol.fsUuid);
+            }
+
+            return mPM.movePackage(packageName, volumeUuid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    @UnsupportedAppUsage
+    public @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app) {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        return getPackageCurrentVolume(app, storage);
+    }
+
+    @VisibleForTesting
+    protected @Nullable VolumeInfo getPackageCurrentVolume(ApplicationInfo app,
+            StorageManager storage) {
+        if (app.isInternal()) {
+            return storage.findVolumeById(VolumeInfo.ID_PRIVATE_INTERNAL);
+        } else {
+            return storage.findVolumeByUuid(app.volumeUuid);
+        }
+    }
+
+    @Override
+    public @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app) {
+        final StorageManager storageManager = mContext.getSystemService(StorageManager.class);
+        return getPackageCandidateVolumes(app, storageManager, mPM);
+    }
+
+    @VisibleForTesting
+    protected @NonNull List<VolumeInfo> getPackageCandidateVolumes(ApplicationInfo app,
+            StorageManager storageManager, IPackageManager pm) {
+        final VolumeInfo currentVol = getPackageCurrentVolume(app, storageManager);
+        final List<VolumeInfo> vols = storageManager.getVolumes();
+        final List<VolumeInfo> candidates = new ArrayList<>();
+        for (VolumeInfo vol : vols) {
+            if (Objects.equals(vol, currentVol)
+                    || isPackageCandidateVolume(mContext, app, vol, pm)) {
+                candidates.add(vol);
+            }
+        }
+        return candidates;
+    }
+
+    @VisibleForTesting
+    protected boolean isForceAllowOnExternal(Context context) {
+        return Settings.Global.getInt(
+                context.getContentResolver(), Settings.Global.FORCE_ALLOW_ON_EXTERNAL, 0) != 0;
+    }
+
+    @VisibleForTesting
+    protected boolean isAllow3rdPartyOnInternal(Context context) {
+        return context.getResources().getBoolean(
+                com.android.internal.R.bool.config_allow3rdPartyAppOnInternal);
+    }
+
+    private boolean isPackageCandidateVolume(
+            ContextImpl context, ApplicationInfo app, VolumeInfo vol, IPackageManager pm) {
+        final boolean forceAllowOnExternal = isForceAllowOnExternal(context);
+
+        if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
+            return app.isSystemApp() || isAllow3rdPartyOnInternal(context);
+        }
+
+        // System apps and apps demanding internal storage can't be moved
+        // anywhere else
+        if (app.isSystemApp()) {
+            return false;
+        }
+        if (!forceAllowOnExternal
+                && (app.installLocation == PackageInfo.INSTALL_LOCATION_INTERNAL_ONLY
+                        || app.installLocation == PackageInfo.INSTALL_LOCATION_UNSPECIFIED)) {
+            return false;
+        }
+
+        // Gotta be able to write there
+        if (!vol.isMountedWritable()) {
+            return false;
+        }
+
+        // Moving into an ASEC on public primary is only option internal
+        if (vol.isPrimaryPhysical()) {
+            return app.isInternal();
+        }
+
+        // Some apps can't be moved. (e.g. device admins)
+        try {
+            if (pm.isPackageDeviceAdminOnAnyUser(app.packageName)) {
+                return false;
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+
+        // Otherwise we can move to any private volume
+        return (vol.getType() == VolumeInfo.TYPE_PRIVATE);
+    }
+
+    @Override
+    public int movePrimaryStorage(VolumeInfo vol) {
+        try {
+            final String volumeUuid;
+            if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.id)) {
+                volumeUuid = StorageManager.UUID_PRIVATE_INTERNAL;
+            } else if (vol.isPrimaryPhysical()) {
+                volumeUuid = StorageManager.UUID_PRIMARY_PHYSICAL;
+            } else {
+                volumeUuid = Preconditions.checkNotNull(vol.fsUuid);
+            }
+
+            return mPM.movePrimaryStorage(volumeUuid);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public @Nullable VolumeInfo getPrimaryStorageCurrentVolume() {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        final String volumeUuid = storage.getPrimaryStorageUuid();
+        return storage.findVolumeByQualifiedUuid(volumeUuid);
+    }
+
+    @Override
+    public @NonNull List<VolumeInfo> getPrimaryStorageCandidateVolumes() {
+        final StorageManager storage = mContext.getSystemService(StorageManager.class);
+        final VolumeInfo currentVol = getPrimaryStorageCurrentVolume();
+        final List<VolumeInfo> vols = storage.getVolumes();
+        final List<VolumeInfo> candidates = new ArrayList<>();
+        if (Objects.equals(StorageManager.UUID_PRIMARY_PHYSICAL,
+                storage.getPrimaryStorageUuid()) && currentVol != null) {
+            // TODO: support moving primary physical to emulated volume
+            candidates.add(currentVol);
+        } else {
+            for (VolumeInfo vol : vols) {
+                if (Objects.equals(vol, currentVol) || isPrimaryStorageCandidateVolume(vol)) {
+                    candidates.add(vol);
+                }
+            }
+        }
+        return candidates;
+    }
+
+    private static boolean isPrimaryStorageCandidateVolume(VolumeInfo vol) {
+        // Private internal is always an option
+        if (VolumeInfo.ID_PRIVATE_INTERNAL.equals(vol.getId())) {
+            return true;
+        }
+
+        // Gotta be able to write there
+        if (!vol.isMountedWritable()) {
+            return false;
+        }
+
+        // We can move to any private volume
+        return (vol.getType() == VolumeInfo.TYPE_PRIVATE);
+    }
+
+    @Override
+    @UnsupportedAppUsage
+    public void deletePackage(String packageName, IPackageDeleteObserver observer, int flags) {
+        deletePackageAsUser(packageName, observer, flags, getUserId());
+    }
+
+    @Override
+    public void deletePackageAsUser(String packageName, IPackageDeleteObserver observer,
+            int flags, int userId) {
+        try {
+            mPM.deletePackageAsUser(packageName, PackageManager.VERSION_CODE_HIGHEST,
+                    observer, userId, flags);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void clearApplicationUserData(String packageName,
+                                         IPackageDataObserver observer) {
+        try {
+            mPM.clearApplicationUserData(packageName, observer, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+    @Override
+    public void deleteApplicationCacheFiles(String packageName,
+                                            IPackageDataObserver observer) {
+        try {
+            mPM.deleteApplicationCacheFiles(packageName, observer);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void deleteApplicationCacheFilesAsUser(String packageName, int userId,
+            IPackageDataObserver observer) {
+        try {
+            mPM.deleteApplicationCacheFilesAsUser(packageName, userId, observer);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void freeStorageAndNotify(String volumeUuid, long idealStorageSize,
+            IPackageDataObserver observer) {
+        try {
+            mPM.freeStorageAndNotify(volumeUuid, idealStorageSize, 0, observer);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void freeStorage(String volumeUuid, long freeStorageSize, IntentSender pi) {
+        try {
+            mPM.freeStorage(volumeUuid, freeStorageSize, 0, pi);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public String[] setDistractingPackageRestrictions(String[] packages, int distractionFlags) {
+        try {
+            return mPM.setDistractingPackageRestrictionsAsUser(packages, distractionFlags,
+                    mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public String[] setPackagesSuspended(String[] packageNames, boolean suspended,
+            PersistableBundle appExtras, PersistableBundle launcherExtras,
+            String dialogMessage) {
+        final SuspendDialogInfo dialogInfo = !TextUtils.isEmpty(dialogMessage)
+                ? new SuspendDialogInfo.Builder().setMessage(dialogMessage).build()
+                : null;
+        return setPackagesSuspended(packageNames, suspended, appExtras, launcherExtras, dialogInfo);
+    }
+
+    @Override
+    public String[] setPackagesSuspended(String[] packageNames, boolean suspended,
+            PersistableBundle appExtras, PersistableBundle launcherExtras,
+            SuspendDialogInfo dialogInfo) {
+        try {
+            return mPM.setPackagesSuspendedAsUser(packageNames, suspended, appExtras,
+                    launcherExtras, dialogInfo, mContext.getOpPackageName(),
+                    getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public String[] getUnsuspendablePackages(String[] packageNames) {
+        try {
+            return mPM.getUnsuspendablePackagesForUser(packageNames, mContext.getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public Bundle getSuspendedPackageAppExtras() {
+        final PersistableBundle extras;
+        try {
+            extras = mPM.getSuspendedPackageAppExtras(mContext.getOpPackageName(),
+                    getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return extras != null ? new Bundle(extras.deepCopy()) : null;
+    }
+
+    @Override
+    public boolean isPackageSuspendedForUser(String packageName, int userId) {
+        try {
+            return mPM.isPackageSuspendedForUser(packageName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @Override
+    public boolean isPackageSuspended(String packageName) throws NameNotFoundException {
+        try {
+            return isPackageSuspendedForUser(packageName, getUserId());
+        } catch (IllegalArgumentException ie) {
+            throw new NameNotFoundException(packageName);
+        }
+    }
+
+    @Override
+    public boolean isPackageSuspended() {
+        return isPackageSuspendedForUser(mContext.getOpPackageName(), getUserId());
+    }
+
+    /** @hide */
+    @Override
+    public void setApplicationCategoryHint(String packageName, int categoryHint) {
+        try {
+            mPM.setApplicationCategoryHint(packageName, categoryHint,
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    @UnsupportedAppUsage
+    public void getPackageSizeInfoAsUser(String packageName, int userHandle,
+            IPackageStatsObserver observer) {
+        final String msg = "Shame on you for calling the hidden API "
+                + "getPackageSizeInfoAsUser(). Shame!";
+        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.O) {
+            throw new UnsupportedOperationException(msg);
+        } else if (observer != null) {
+            Log.d(TAG, msg);
+            try {
+                observer.onGetStatsCompleted(null, false);
+            } catch (RemoteException ignored) {
+            }
+        }
+    }
+
+    @Override
+    public void addPackageToPreferred(String packageName) {
+        Log.w(TAG, "addPackageToPreferred() is a no-op");
+    }
+
+    @Override
+    public void removePackageFromPreferred(String packageName) {
+        Log.w(TAG, "removePackageFromPreferred() is a no-op");
+    }
+
+    @Override
+    public List<PackageInfo> getPreferredPackages(int flags) {
+        Log.w(TAG, "getPreferredPackages() is a no-op");
+        return Collections.emptyList();
+    }
+
+    @Override
+    public void addPreferredActivity(IntentFilter filter,
+                                     int match, ComponentName[] set, ComponentName activity) {
+        try {
+            mPM.addPreferredActivity(filter, match, set, activity, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void addPreferredActivityAsUser(IntentFilter filter, int match,
+            ComponentName[] set, ComponentName activity, int userId) {
+        try {
+            mPM.addPreferredActivity(filter, match, set, activity, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void replacePreferredActivity(IntentFilter filter,
+                                         int match, ComponentName[] set, ComponentName activity) {
+        try {
+            mPM.replacePreferredActivity(filter, match, set, activity, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void replacePreferredActivityAsUser(IntentFilter filter,
+                                         int match, ComponentName[] set, ComponentName activity,
+                                         int userId) {
+        try {
+            mPM.replacePreferredActivity(filter, match, set, activity, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void clearPackagePreferredActivities(String packageName) {
+        try {
+            mPM.clearPackagePreferredActivities(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int getPreferredActivities(List<IntentFilter> outFilters,
+                                      List<ComponentName> outActivities, String packageName) {
+        try {
+            return mPM.getPreferredActivities(outFilters, outActivities, packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public ComponentName getHomeActivities(List<ResolveInfo> outActivities) {
+        try {
+            return mPM.getHomeActivities(outActivities);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void setSyntheticAppDetailsActivityEnabled(String packageName, boolean enabled) {
+        try {
+            ComponentName componentName = new ComponentName(packageName,
+                    PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME);
+            mPM.setComponentEnabledSetting(componentName, enabled
+                    ? PackageManager.COMPONENT_ENABLED_STATE_DEFAULT
+                    : PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
+                    PackageManager.DONT_KILL_APP, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean getSyntheticAppDetailsActivityEnabled(String packageName) {
+        try {
+            ComponentName componentName = new ComponentName(packageName,
+                    PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME);
+            int state = mPM.getComponentEnabledSetting(componentName, getUserId());
+            return state == PackageManager.COMPONENT_ENABLED_STATE_ENABLED
+                    || state == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void setComponentEnabledSetting(ComponentName componentName,
+                                           int newState, int flags) {
+        try {
+            mPM.setComponentEnabledSetting(componentName, newState, flags, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int getComponentEnabledSetting(ComponentName componentName) {
+        try {
+            return mPM.getComponentEnabledSetting(componentName, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void setApplicationEnabledSetting(String packageName,
+                                             int newState, int flags) {
+        try {
+            mPM.setApplicationEnabledSetting(packageName, newState, flags,
+                    getUserId(), mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public int getApplicationEnabledSetting(String packageName) {
+        try {
+            return mPM.getApplicationEnabledSetting(packageName, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public void flushPackageRestrictionsAsUser(int userId) {
+        try {
+            mPM.flushPackageRestrictionsAsUser(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean setApplicationHiddenSettingAsUser(String packageName, boolean hidden,
+            UserHandle user) {
+        try {
+            return mPM.setApplicationHiddenSettingAsUser(packageName, hidden,
+                    user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean getApplicationHiddenSettingAsUser(String packageName, UserHandle user) {
+        try {
+            return mPM.getApplicationHiddenSettingAsUser(packageName, user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @Override
+    public KeySet getKeySetByAlias(String packageName, String alias) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkNotNull(alias);
+        try {
+            return mPM.getKeySetByAlias(packageName, alias);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @Override
+    public KeySet getSigningKeySet(String packageName) {
+        Preconditions.checkNotNull(packageName);
+        try {
+            return mPM.getSigningKeySet(packageName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @Override
+    public boolean isSignedBy(String packageName, KeySet ks) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkNotNull(ks);
+        try {
+            return mPM.isPackageSignedByKeySet(packageName, ks);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** @hide */
+    @Override
+    public boolean isSignedByExactly(String packageName, KeySet ks) {
+        Preconditions.checkNotNull(packageName);
+        Preconditions.checkNotNull(ks);
+        try {
+            return mPM.isPackageSignedByKeySetExactly(packageName, ks);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public VerifierDeviceIdentity getVerifierDeviceIdentity() {
+        try {
+            return mPM.getVerifierDeviceIdentity();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public boolean isUpgrade() {
+        return isDeviceUpgrading();
+    }
+
+    @Override
+    public boolean isDeviceUpgrading() {
+        try {
+            return mPM.isDeviceUpgrading();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @Override
+    public PackageInstaller getPackageInstaller() {
+        synchronized (mLock) {
+            if (mInstaller == null) {
+                try {
+                    mInstaller = new PackageInstaller(mPM.getPackageInstaller(),
+                            mContext.getPackageName(), getUserId());
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            return mInstaller;
+        }
+    }
+
+    @Override
+    public boolean isPackageAvailable(String packageName) {
+        try {
+            return mPM.isPackageAvailable(packageName, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void addCrossProfileIntentFilter(IntentFilter filter, int sourceUserId, int targetUserId,
+            int flags) {
+        try {
+            mPM.addCrossProfileIntentFilter(filter, mContext.getOpPackageName(),
+                    sourceUserId, targetUserId, flags);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void clearCrossProfileIntentFilters(int sourceUserId) {
+        try {
+            mPM.clearCrossProfileIntentFilters(sourceUserId, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public Drawable loadItemIcon(PackageItemInfo itemInfo, ApplicationInfo appInfo) {
+        Drawable dr = loadUnbadgedItemIcon(itemInfo, appInfo);
+        if (itemInfo.showUserIcon != UserHandle.USER_NULL) {
+            return dr;
+        }
+        return getUserBadgedIcon(dr, new UserHandle(getUserId()));
+    }
+
+    /**
+     * @hide
+     */
+    public Drawable loadUnbadgedItemIcon(@NonNull PackageItemInfo itemInfo,
+            @Nullable ApplicationInfo appInfo) {
+        if (itemInfo.showUserIcon != UserHandle.USER_NULL) {
+            // Indicates itemInfo is for a different user (e.g. a profile's parent), so use a 
+            // generic user icon (users generally lack permission to view each other's actual icons)
+            int targetUserId = itemInfo.showUserIcon;
+            return UserIcons.getDefaultUserIcon(
+                    mContext.getResources(), targetUserId, /* light= */ false);
+        }
+        Drawable dr = null;
+        if (itemInfo.packageName != null) {
+            dr = getDrawable(itemInfo.packageName, itemInfo.icon, appInfo);
+        }
+        if (dr == null && itemInfo != appInfo && appInfo != null) {
+            dr = loadUnbadgedItemIcon(appInfo, appInfo);
+        }
+        if (dr == null) {
+            dr = itemInfo.loadDefaultIcon(this);
+        }
+        return dr;
+    }
+
+    private Drawable getBadgedDrawable(Drawable drawable, Drawable badgeDrawable,
+            Rect badgeLocation, boolean tryBadgeInPlace) {
+        final int badgedWidth = drawable.getIntrinsicWidth();
+        final int badgedHeight = drawable.getIntrinsicHeight();
+        final boolean canBadgeInPlace = tryBadgeInPlace
+                && (drawable instanceof BitmapDrawable)
+                && ((BitmapDrawable) drawable).getBitmap().isMutable();
+
+        final Bitmap bitmap;
+        if (canBadgeInPlace) {
+            bitmap = ((BitmapDrawable) drawable).getBitmap();
+        } else {
+            bitmap = Bitmap.createBitmap(badgedWidth, badgedHeight, Bitmap.Config.ARGB_8888);
+        }
+        Canvas canvas = new Canvas(bitmap);
+
+        if (!canBadgeInPlace) {
+            drawable.setBounds(0, 0, badgedWidth, badgedHeight);
+            drawable.draw(canvas);
+        }
+
+        if (badgeLocation != null) {
+            if (badgeLocation.left < 0 || badgeLocation.top < 0
+                    || badgeLocation.width() > badgedWidth || badgeLocation.height() > badgedHeight) {
+                throw new IllegalArgumentException("Badge location " + badgeLocation
+                        + " not in badged drawable bounds "
+                        + new Rect(0, 0, badgedWidth, badgedHeight));
+            }
+            badgeDrawable.setBounds(0, 0, badgeLocation.width(), badgeLocation.height());
+
+            canvas.save();
+            canvas.translate(badgeLocation.left, badgeLocation.top);
+            badgeDrawable.draw(canvas);
+            canvas.restore();
+        } else {
+            badgeDrawable.setBounds(0, 0, badgedWidth, badgedHeight);
+            badgeDrawable.draw(canvas);
+        }
+
+        if (!canBadgeInPlace) {
+            BitmapDrawable mergedDrawable = new BitmapDrawable(mContext.getResources(), bitmap);
+
+            if (drawable instanceof BitmapDrawable) {
+                BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+                mergedDrawable.setTargetDensity(bitmapDrawable.getBitmap().getDensity());
+            }
+
+            return mergedDrawable;
+        }
+
+        return drawable;
+    }
+
+    private boolean isManagedProfile(int userId) {
+        return getUserManager().isManagedProfile(userId);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public int getInstallReason(String packageName, UserHandle user) {
+        try {
+            return mPM.getInstallReason(packageName, user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /** {@hide} */
+    private static class MoveCallbackDelegate extends IPackageMoveObserver.Stub implements
+            Handler.Callback {
+        private static final int MSG_CREATED = 1;
+        private static final int MSG_STATUS_CHANGED = 2;
+
+        final MoveCallback mCallback;
+        final Handler mHandler;
+
+        public MoveCallbackDelegate(MoveCallback callback, Looper looper) {
+            mCallback = callback;
+            mHandler = new Handler(looper, this);
+        }
+
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_CREATED: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    mCallback.onCreated(args.argi1, (Bundle) args.arg2);
+                    args.recycle();
+                    return true;
+                }
+                case MSG_STATUS_CHANGED: {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    mCallback.onStatusChanged(args.argi1, args.argi2, (long) args.arg3);
+                    args.recycle();
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        @Override
+        public void onCreated(int moveId, Bundle extras) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.argi1 = moveId;
+            args.arg2 = extras;
+            mHandler.obtainMessage(MSG_CREATED, args).sendToTarget();
+        }
+
+        @Override
+        public void onStatusChanged(int moveId, int status, long estMillis) {
+            final SomeArgs args = SomeArgs.obtain();
+            args.argi1 = moveId;
+            args.argi2 = status;
+            args.arg3 = estMillis;
+            mHandler.obtainMessage(MSG_STATUS_CHANGED, args).sendToTarget();
+        }
+    }
+
+    private final ContextImpl mContext;
+    @UnsupportedAppUsage
+    private final IPackageManager mPM;
+
+    /** Assume locked until we hear otherwise */
+    private volatile boolean mUserUnlocked = false;
+
+    private static final Object sSync = new Object();
+    private static ArrayMap<ResourceName, WeakReference<Drawable.ConstantState>> sIconCache
+            = new ArrayMap<ResourceName, WeakReference<Drawable.ConstantState>>();
+    private static ArrayMap<ResourceName, WeakReference<CharSequence>> sStringCache
+            = new ArrayMap<ResourceName, WeakReference<CharSequence>>();
+
+    private final Map<OnPermissionsChangedListener, IOnPermissionsChangeListener>
+            mPermissionListeners = new ArrayMap<>();
+
+    public class OnPermissionsChangeListenerDelegate extends IOnPermissionsChangeListener.Stub
+            implements Handler.Callback{
+        private static final int MSG_PERMISSIONS_CHANGED = 1;
+
+        private final OnPermissionsChangedListener mListener;
+        private final Handler mHandler;
+
+
+        public OnPermissionsChangeListenerDelegate(OnPermissionsChangedListener listener,
+                Looper looper) {
+            mListener = listener;
+            mHandler = new Handler(looper, this);
+        }
+
+        @Override
+        public void onPermissionsChanged(int uid) {
+            mHandler.obtainMessage(MSG_PERMISSIONS_CHANGED, uid, 0).sendToTarget();
+        }
+
+        @Override
+        public boolean handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_PERMISSIONS_CHANGED: {
+                    final int uid = msg.arg1;
+                    mListener.onPermissionsChanged(uid);
+                    return true;
+                }
+            }
+            return false;
+        }
+    }
+
+    @Override
+    public boolean canRequestPackageInstalls() {
+        try {
+            return mPM.canRequestPackageInstalls(mContext.getPackageName(), getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public ComponentName getInstantAppResolverSettingsComponent() {
+        try {
+            return mPM.getInstantAppResolverSettingsComponent();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public ComponentName getInstantAppInstallerComponent() {
+        try {
+            return mPM.getInstantAppInstallerComponent();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public String getInstantAppAndroidId(String packageName, UserHandle user) {
+        try {
+            return mPM.getInstantAppAndroidId(packageName, user.getIdentifier());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    private static class DexModuleRegisterResult {
+        final String dexModulePath;
+        final boolean success;
+        final String message;
+
+        private DexModuleRegisterResult(String dexModulePath, boolean success, String message) {
+            this.dexModulePath = dexModulePath;
+            this.success = success;
+            this.message = message;
+        }
+    }
+
+    private static class DexModuleRegisterCallbackDelegate
+            extends android.content.pm.IDexModuleRegisterCallback.Stub
+            implements Handler.Callback {
+        private static final int MSG_DEX_MODULE_REGISTERED = 1;
+        private final DexModuleRegisterCallback callback;
+        private final Handler mHandler;
+
+        DexModuleRegisterCallbackDelegate(@NonNull DexModuleRegisterCallback callback) {
+            this.callback = callback;
+            mHandler = new Handler(Looper.getMainLooper(), this);
+        }
+
+        @Override
+        public void onDexModuleRegistered(@NonNull String dexModulePath, boolean success,
+                @Nullable String message)throws RemoteException {
+            mHandler.obtainMessage(MSG_DEX_MODULE_REGISTERED,
+                    new DexModuleRegisterResult(dexModulePath, success, message)).sendToTarget();
+        }
+
+        @Override
+        public boolean handleMessage(Message msg) {
+            if (msg.what != MSG_DEX_MODULE_REGISTERED) {
+                return false;
+            }
+            DexModuleRegisterResult result = (DexModuleRegisterResult)msg.obj;
+            callback.onDexModuleRegistered(result.dexModulePath, result.success, result.message);
+            return true;
+        }
+    }
+
+    @Override
+    public void registerDexModule(@NonNull String dexModule,
+            @Nullable DexModuleRegisterCallback callback) {
+        // Check if this is a shared module by looking if the others can read it.
+        boolean isSharedModule = false;
+        try {
+            StructStat stat = Os.stat(dexModule);
+            if ((OsConstants.S_IROTH & stat.st_mode) != 0) {
+                isSharedModule = true;
+            }
+        } catch (ErrnoException e) {
+            callback.onDexModuleRegistered(dexModule, false,
+                    "Could not get stat the module file: " + e.getMessage());
+            return;
+        }
+
+        // Module path is ok.
+        // Create the callback delegate to be passed to package manager service.
+        DexModuleRegisterCallbackDelegate callbackDelegate = null;
+        if (callback != null) {
+            callbackDelegate = new DexModuleRegisterCallbackDelegate(callback);
+        }
+
+        // Invoke the package manager service.
+        try {
+            mPM.registerDexModule(mContext.getPackageName(), dexModule,
+                    isSharedModule, callbackDelegate);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public CharSequence getHarmfulAppWarning(String packageName) {
+        try {
+            return mPM.getHarmfulAppWarning(packageName, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public void setHarmfulAppWarning(String packageName, CharSequence warning) {
+        try {
+            mPM.setHarmfulAppWarning(packageName, warning, getUserId());
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public ArtManager getArtManager() {
+        synchronized (mLock) {
+            if (mArtManager == null) {
+                try {
+                    mArtManager = new ArtManager(mContext, mPM.getArtManager());
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            return mArtManager;
+        }
+    }
+
+    @Override
+    public String getSystemTextClassifierPackageName() {
+        try {
+            return mPM.getSystemTextClassifierPackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public String getAttentionServicePackageName() {
+        try {
+            return mPM.getAttentionServicePackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public String getWellbeingPackageName() {
+        try {
+            return mPM.getWellbeingPackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public String getAppPredictionServicePackageName() {
+        try {
+            return mPM.getAppPredictionServicePackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public String getSystemCaptionsServicePackageName() {
+        try {
+            return mPM.getSystemCaptionsServicePackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public String getIncidentReportApproverPackageName() {
+        try {
+            return mPM.getIncidentReportApproverPackageName();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    @Override
+    public boolean isPackageStateProtected(String packageName, int userId) {
+        try {
+            return mPM.isPackageStateProtected(packageName, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    public void sendDeviceCustomizationReadyBroadcast() {
+        try {
+            mPM.sendDeviceCustomizationReadyBroadcast();
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+}
diff --git a/android/app/ApplicationThreadConstants.java b/android/app/ApplicationThreadConstants.java
new file mode 100644
index 0000000..1fa670f
--- /dev/null
+++ b/android/app/ApplicationThreadConstants.java
@@ -0,0 +1,39 @@
+/*
+ * 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.app;
+
+/**
+ * @hide
+ */
+public final class ApplicationThreadConstants {
+    public static final int BACKUP_MODE_INCREMENTAL = 0;
+    public static final int BACKUP_MODE_FULL = 1;
+    public static final int BACKUP_MODE_RESTORE = 2;
+    public static final int BACKUP_MODE_RESTORE_FULL = 3;
+
+    public static final int DEBUG_OFF = 0;
+    public static final int DEBUG_ON = 1;
+    public static final int DEBUG_WAIT = 2;
+
+    // the package has been removed, clean up internal references
+    public static final int PACKAGE_REMOVED = 0;
+    public static final int EXTERNAL_STORAGE_UNAVAILABLE = 1;
+    // the package is being modified in-place, don't kill it and retain references to it
+    public static final int PACKAGE_REMOVED_DONT_KILL = 2;
+    // a previously removed package was replaced with a new version [eg. upgrade, split added, ...]
+    public static final int PACKAGE_REPLACED = 3;
+}
\ No newline at end of file
diff --git a/android/app/AuthenticationRequiredException.java b/android/app/AuthenticationRequiredException.java
new file mode 100644
index 0000000..0d87336
--- /dev/null
+++ b/android/app/AuthenticationRequiredException.java
@@ -0,0 +1,96 @@
+/*
+ * 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.app;
+
+import android.content.ContentProvider;
+import android.content.ContentResolver;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Specialization of {@link SecurityException} that is thrown when authentication is needed from the
+ * end user before viewing the content.
+ * <p>
+ * This exception is only appropriate where there is a concrete action the user can take to
+ * authorize and make forward progress, such as confirming or entering authentication credentials,
+ * or granting access via other means.
+ * <p class="note">
+ * Note: legacy code that receives this exception may treat it as a general
+ * {@link SecurityException}, and thus there is no guarantee that the action contained will be
+ * invoked by the user.
+ * </p>
+ */
+public final class AuthenticationRequiredException extends SecurityException implements Parcelable {
+    private static final String TAG = "AuthenticationRequiredException";
+
+    private final PendingIntent mUserAction;
+
+    /** {@hide} */
+    public AuthenticationRequiredException(Parcel in) {
+        this(new SecurityException(in.readString()), PendingIntent.CREATOR.createFromParcel(in));
+    }
+
+    /**
+     * Create an instance ready to be thrown.
+     *
+     * @param cause original cause with details designed for engineering
+     *            audiences.
+     * @param userAction primary action that will initiate the recovery. This
+     *            must launch an activity that is expected to set
+     *            {@link Activity#setResult(int)} before finishing to
+     *            communicate the final status of the recovery. For example,
+     *            apps that observe {@link Activity#RESULT_OK} may choose to
+     *            immediately retry their operation.
+     */
+    public AuthenticationRequiredException(Throwable cause, PendingIntent userAction) {
+        super(cause.getMessage());
+        mUserAction = Preconditions.checkNotNull(userAction);
+    }
+
+    /**
+     * Return primary action that will initiate the authorization.
+     */
+    public PendingIntent getUserAction() {
+        return mUserAction;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeString(getMessage());
+        mUserAction.writeToParcel(dest, flags);
+    }
+
+    public static final @android.annotation.NonNull Creator<AuthenticationRequiredException> CREATOR =
+            new Creator<AuthenticationRequiredException>() {
+        @Override
+        public AuthenticationRequiredException createFromParcel(Parcel source) {
+            return new AuthenticationRequiredException(source);
+        }
+
+        @Override
+        public AuthenticationRequiredException[] newArray(int size) {
+            return new AuthenticationRequiredException[size];
+        }
+    };
+}
diff --git a/android/app/AutomaticZenRule.java b/android/app/AutomaticZenRule.java
new file mode 100644
index 0000000..7180c01
--- /dev/null
+++ b/android/app/AutomaticZenRule.java
@@ -0,0 +1,317 @@
+/**
+ * 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.app;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.NotificationManager.InterruptionFilter;
+import android.content.ComponentName;
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.service.notification.Condition;
+import android.service.notification.ZenPolicy;
+
+import java.util.Objects;
+
+/**
+ * Rule instance information for zen mode.
+ */
+public final class AutomaticZenRule implements Parcelable {
+    /* @hide */
+    private static final int ENABLED = 1;
+    /* @hide */
+    private static final int DISABLED = 0;
+    private boolean enabled = false;
+    private String name;
+    private @InterruptionFilter int interruptionFilter;
+    private Uri conditionId;
+    private ComponentName owner;
+    private ComponentName configurationActivity;
+    private long creationTime;
+    private ZenPolicy mZenPolicy;
+    private boolean mModified = false;
+
+    /**
+     * Creates an automatic zen rule.
+     *
+     * @param name The name of the rule.
+     * @param owner The Condition Provider service that owns this rule.
+     * @param interruptionFilter The interruption filter defines which notifications are allowed to
+     *                           interrupt the user (e.g. via sound &amp; vibration) while this rule
+     *                           is active.
+     * @param enabled Whether the rule is enabled.
+     * @deprecated use {@link #AutomaticZenRule(String, ComponentName, ComponentName, Uri,
+     * ZenPolicy, int, boolean)}.
+     */
+    @Deprecated
+    public AutomaticZenRule(String name, ComponentName owner, Uri conditionId,
+            int interruptionFilter, boolean enabled) {
+        this(name, owner, null, conditionId, null, interruptionFilter, enabled);
+    }
+
+    /**
+     * Creates an automatic zen rule.
+     *
+     * @param name The name of the rule.
+     * @param owner The Condition Provider service that owns this rule. This can be null if you're
+     *              using {@link NotificationManager#setAutomaticZenRuleState(String, Condition)}
+     *              instead of {@link android.service.notification.ConditionProviderService}.
+     * @param configurationActivity An activity that handles
+     *                              {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows
+     *                              the user
+     *                              more information about this rule and/or allows them to
+     *                              configure it. This is required if you are not using a
+     *                              {@link android.service.notification.ConditionProviderService}.
+     *                              If you are, it overrides the information specified in your
+     *                              manifest.
+     * @param conditionId A representation of the state that should cause your app to apply the
+     *                    given interruption filter.
+     * @param interruptionFilter The interruption filter defines which notifications are allowed to
+     *                           interrupt the user (e.g. via sound &amp; vibration) while this rule
+     *                           is active.
+     * @param policy The policy defines which notifications are allowed to interrupt the user
+     *               while this rule is active. This overrides the global policy while this rule is
+     *               action ({@link Condition#STATE_TRUE}).
+     * @param enabled Whether the rule is enabled.
+     */
+    public AutomaticZenRule(@NonNull String name, @Nullable ComponentName owner,
+            @Nullable ComponentName configurationActivity, @NonNull Uri conditionId,
+            @Nullable ZenPolicy policy, int interruptionFilter, boolean enabled) {
+        this.name = name;
+        this.owner = owner;
+        this.configurationActivity = configurationActivity;
+        this.conditionId = conditionId;
+        this.interruptionFilter = interruptionFilter;
+        this.enabled = enabled;
+        this.mZenPolicy = policy;
+    }
+
+    /**
+     * @hide
+     */
+    public AutomaticZenRule(String name, ComponentName owner, ComponentName configurationActivity,
+            Uri conditionId, ZenPolicy policy, int interruptionFilter, boolean enabled,
+            long creationTime) {
+        this(name, owner, configurationActivity, conditionId, policy, interruptionFilter, enabled);
+        this.creationTime = creationTime;
+    }
+
+    public AutomaticZenRule(Parcel source) {
+        enabled = source.readInt() == ENABLED;
+        if (source.readInt() == ENABLED) {
+            name = source.readString();
+        }
+        interruptionFilter = source.readInt();
+        conditionId = source.readParcelable(null);
+        owner = source.readParcelable(null);
+        configurationActivity = source.readParcelable(null);
+        creationTime = source.readLong();
+        mZenPolicy = source.readParcelable(null);
+        mModified = source.readInt() == ENABLED;
+    }
+
+    /**
+     * Returns the {@link ComponentName} of the condition provider service that owns this rule.
+     */
+    public ComponentName getOwner() {
+        return owner;
+    }
+
+    /**
+     * Returns the {@link ComponentName} of the activity that shows configuration options
+     * for this rule.
+     */
+    public @Nullable ComponentName getConfigurationActivity() {
+        return configurationActivity;
+    }
+
+    /**
+     * Returns the representation of the state that causes this rule to become active.
+     */
+    public Uri getConditionId() {
+        return conditionId;
+    }
+
+    /**
+     * Returns the interruption filter that is applied when this rule is active.
+     */
+    public int getInterruptionFilter() {
+        return interruptionFilter;
+    }
+
+    /**
+     * Returns the name of this rule.
+     */
+    public String getName() {
+        return name;
+    }
+
+    /**
+     * Returns whether this rule is enabled.
+     */
+    public boolean isEnabled() {
+        return enabled;
+    }
+
+    /**
+     * Returns whether this rule's name has been modified by the user.
+     * @hide
+     */
+    public boolean isModified() {
+        return mModified;
+    }
+
+    /**
+     * Gets the zen policy.
+     */
+    public ZenPolicy getZenPolicy() {
+        return mZenPolicy == null ? null : this.mZenPolicy.copy();
+    }
+
+    /**
+     * Returns the time this rule was created, represented as milliseconds since the epoch.
+     */
+    public long getCreationTime() {
+      return creationTime;
+    }
+
+    /**
+     * Sets the representation of the state that causes this rule to become active.
+     */
+    public void setConditionId(Uri conditionId) {
+        this.conditionId = conditionId;
+    }
+
+    /**
+     * Sets the interruption filter that is applied when this rule is active.
+     * @param interruptionFilter The do not disturb mode to enter when this rule is active.
+     */
+    public void setInterruptionFilter(@InterruptionFilter int interruptionFilter) {
+        this.interruptionFilter = interruptionFilter;
+    }
+
+    /**
+     * Sets the name of this rule.
+     */
+    public void setName(String name) {
+        this.name = name;
+    }
+
+    /**
+     * Enables this rule.
+     */
+    public void setEnabled(boolean enabled) {
+        this.enabled = enabled;
+    }
+
+    /**
+     * Sets modified state of this rule.
+     * @hide
+     */
+    public void setModified(boolean modified) {
+        this.mModified = modified;
+    }
+
+    /**
+     * Sets the zen policy.
+     */
+    public void setZenPolicy(ZenPolicy zenPolicy) {
+        this.mZenPolicy = (zenPolicy == null ? null : zenPolicy.copy());
+    }
+
+    /**
+     * Sets the configuration activity - an activity that handles
+     * {@link NotificationManager#ACTION_AUTOMATIC_ZEN_RULE} that shows the user more information
+     * about this rule and/or allows them to configure it. This is required to be non-null for rules
+     * that are not backed by {@link android.service.notification.ConditionProviderService}.
+     */
+    public void setConfigurationActivity(@Nullable ComponentName componentName) {
+        this.configurationActivity = componentName;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(enabled ? ENABLED : DISABLED);
+        if (name != null) {
+            dest.writeInt(1);
+            dest.writeString(name);
+        } else {
+            dest.writeInt(0);
+        }
+        dest.writeInt(interruptionFilter);
+        dest.writeParcelable(conditionId, 0);
+        dest.writeParcelable(owner, 0);
+        dest.writeParcelable(configurationActivity, 0);
+        dest.writeLong(creationTime);
+        dest.writeParcelable(mZenPolicy, 0);
+        dest.writeInt(mModified ? ENABLED : DISABLED);
+    }
+
+    @Override
+    public String toString() {
+        return new StringBuilder(AutomaticZenRule.class.getSimpleName()).append('[')
+                .append("enabled=").append(enabled)
+                .append(",name=").append(name)
+                .append(",interruptionFilter=").append(interruptionFilter)
+                .append(",conditionId=").append(conditionId)
+                .append(",owner=").append(owner)
+                .append(",configActivity=").append(configurationActivity)
+                .append(",creationTime=").append(creationTime)
+                .append(",mZenPolicy=").append(mZenPolicy)
+                .append(']').toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof AutomaticZenRule)) return false;
+        if (o == this) return true;
+        final AutomaticZenRule other = (AutomaticZenRule) o;
+        return other.enabled == enabled
+                && other.mModified == mModified
+                && Objects.equals(other.name, name)
+                && other.interruptionFilter == interruptionFilter
+                && Objects.equals(other.conditionId, conditionId)
+                && Objects.equals(other.owner, owner)
+                && Objects.equals(other.mZenPolicy, mZenPolicy)
+                && Objects.equals(other.configurationActivity, configurationActivity)
+                && other.creationTime == creationTime;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(enabled, name, interruptionFilter, conditionId, owner,
+                configurationActivity, mZenPolicy, mModified, creationTime);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<AutomaticZenRule> CREATOR
+            = new Parcelable.Creator<AutomaticZenRule>() {
+        @Override
+        public AutomaticZenRule createFromParcel(Parcel source) {
+            return new AutomaticZenRule(source);
+        }
+        @Override
+        public AutomaticZenRule[] newArray(int size) {
+            return new AutomaticZenRule[size];
+        }
+    };
+}
diff --git a/android/app/BackStackRecord.java b/android/app/BackStackRecord.java
new file mode 100644
index 0000000..351e737
--- /dev/null
+++ b/android/app/BackStackRecord.java
@@ -0,0 +1,1024 @@
+/*
+ * 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.app;
+
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.LogWriter;
+import android.view.View;
+
+import com.android.internal.util.FastPrintWriter;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+
+final class BackStackState implements Parcelable {
+    final int[] mOps;
+    final int mTransition;
+    final int mTransitionStyle;
+    final String mName;
+    final int mIndex;
+    final int mBreadCrumbTitleRes;
+    final CharSequence mBreadCrumbTitleText;
+    final int mBreadCrumbShortTitleRes;
+    final CharSequence mBreadCrumbShortTitleText;
+    final ArrayList<String> mSharedElementSourceNames;
+    final ArrayList<String> mSharedElementTargetNames;
+    final boolean mReorderingAllowed;
+
+    public BackStackState(FragmentManagerImpl fm, BackStackRecord bse) {
+        final int numOps = bse.mOps.size();
+        mOps = new int[numOps * 6];
+
+        if (!bse.mAddToBackStack) {
+            throw new IllegalStateException("Not on back stack");
+        }
+
+        int pos = 0;
+        for (int opNum = 0; opNum < numOps; opNum++) {
+            final BackStackRecord.Op op = bse.mOps.get(opNum);
+            mOps[pos++] = op.cmd;
+            mOps[pos++] = op.fragment != null ? op.fragment.mIndex : -1;
+            mOps[pos++] = op.enterAnim;
+            mOps[pos++] = op.exitAnim;
+            mOps[pos++] = op.popEnterAnim;
+            mOps[pos++] = op.popExitAnim;
+        }
+        mTransition = bse.mTransition;
+        mTransitionStyle = bse.mTransitionStyle;
+        mName = bse.mName;
+        mIndex = bse.mIndex;
+        mBreadCrumbTitleRes = bse.mBreadCrumbTitleRes;
+        mBreadCrumbTitleText = bse.mBreadCrumbTitleText;
+        mBreadCrumbShortTitleRes = bse.mBreadCrumbShortTitleRes;
+        mBreadCrumbShortTitleText = bse.mBreadCrumbShortTitleText;
+        mSharedElementSourceNames = bse.mSharedElementSourceNames;
+        mSharedElementTargetNames = bse.mSharedElementTargetNames;
+        mReorderingAllowed = bse.mReorderingAllowed;
+    }
+
+    public BackStackState(Parcel in) {
+        mOps = in.createIntArray();
+        mTransition = in.readInt();
+        mTransitionStyle = in.readInt();
+        mName = in.readString();
+        mIndex = in.readInt();
+        mBreadCrumbTitleRes = in.readInt();
+        mBreadCrumbTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mBreadCrumbShortTitleRes = in.readInt();
+        mBreadCrumbShortTitleText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mSharedElementSourceNames = in.createStringArrayList();
+        mSharedElementTargetNames = in.createStringArrayList();
+        mReorderingAllowed = in.readInt() != 0;
+    }
+
+    public BackStackRecord instantiate(FragmentManagerImpl fm) {
+        BackStackRecord bse = new BackStackRecord(fm);
+        int pos = 0;
+        int num = 0;
+        while (pos < mOps.length) {
+            BackStackRecord.Op op = new BackStackRecord.Op();
+            op.cmd = mOps[pos++];
+            if (FragmentManagerImpl.DEBUG) {
+                Log.v(FragmentManagerImpl.TAG,
+                        "Instantiate " + bse + " op #" + num + " base fragment #" + mOps[pos]);
+            }
+            int findex = mOps[pos++];
+            if (findex >= 0) {
+                Fragment f = fm.mActive.get(findex);
+                op.fragment = f;
+            } else {
+                op.fragment = null;
+            }
+            op.enterAnim = mOps[pos++];
+            op.exitAnim = mOps[pos++];
+            op.popEnterAnim = mOps[pos++];
+            op.popExitAnim = mOps[pos++];
+            bse.mEnterAnim = op.enterAnim;
+            bse.mExitAnim = op.exitAnim;
+            bse.mPopEnterAnim = op.popEnterAnim;
+            bse.mPopExitAnim = op.popExitAnim;
+            bse.addOp(op);
+            num++;
+        }
+        bse.mTransition = mTransition;
+        bse.mTransitionStyle = mTransitionStyle;
+        bse.mName = mName;
+        bse.mIndex = mIndex;
+        bse.mAddToBackStack = true;
+        bse.mBreadCrumbTitleRes = mBreadCrumbTitleRes;
+        bse.mBreadCrumbTitleText = mBreadCrumbTitleText;
+        bse.mBreadCrumbShortTitleRes = mBreadCrumbShortTitleRes;
+        bse.mBreadCrumbShortTitleText = mBreadCrumbShortTitleText;
+        bse.mSharedElementSourceNames = mSharedElementSourceNames;
+        bse.mSharedElementTargetNames = mSharedElementTargetNames;
+        bse.mReorderingAllowed = mReorderingAllowed;
+        bse.bumpBackStackNesting(1);
+        return bse;
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeIntArray(mOps);
+        dest.writeInt(mTransition);
+        dest.writeInt(mTransitionStyle);
+        dest.writeString(mName);
+        dest.writeInt(mIndex);
+        dest.writeInt(mBreadCrumbTitleRes);
+        TextUtils.writeToParcel(mBreadCrumbTitleText, dest, 0);
+        dest.writeInt(mBreadCrumbShortTitleRes);
+        TextUtils.writeToParcel(mBreadCrumbShortTitleText, dest, 0);
+        dest.writeStringList(mSharedElementSourceNames);
+        dest.writeStringList(mSharedElementTargetNames);
+        dest.writeInt(mReorderingAllowed ? 1 : 0);
+    }
+
+    public static final @android.annotation.NonNull Parcelable.Creator<BackStackState> CREATOR
+            = new Parcelable.Creator<BackStackState>() {
+        public BackStackState createFromParcel(Parcel in) {
+            return new BackStackState(in);
+        }
+
+        public BackStackState[] newArray(int size) {
+            return new BackStackState[size];
+        }
+    };
+}
+
+/**
+ * @hide Entry of an operation on the fragment back stack.
+ */
+final class BackStackRecord extends FragmentTransaction implements
+        FragmentManager.BackStackEntry, FragmentManagerImpl.OpGenerator {
+    static final String TAG = FragmentManagerImpl.TAG;
+
+    final FragmentManagerImpl mManager;
+
+    static final int OP_NULL = 0;
+    static final int OP_ADD = 1;
+    static final int OP_REPLACE = 2;
+    static final int OP_REMOVE = 3;
+    static final int OP_HIDE = 4;
+    static final int OP_SHOW = 5;
+    static final int OP_DETACH = 6;
+    static final int OP_ATTACH = 7;
+    static final int OP_SET_PRIMARY_NAV = 8;
+    static final int OP_UNSET_PRIMARY_NAV = 9;
+
+    static final class Op {
+        int cmd;
+        Fragment fragment;
+        int enterAnim;
+        int exitAnim;
+        int popEnterAnim;
+        int popExitAnim;
+
+        Op() {
+        }
+
+        Op(int cmd, Fragment fragment) {
+            this.cmd = cmd;
+            this.fragment = fragment;
+        }
+    }
+
+    ArrayList<Op> mOps = new ArrayList<>();
+    int mEnterAnim;
+    int mExitAnim;
+    int mPopEnterAnim;
+    int mPopExitAnim;
+    int mTransition;
+    int mTransitionStyle;
+    boolean mAddToBackStack;
+    boolean mAllowAddToBackStack = true;
+    String mName;
+    boolean mCommitted;
+    int mIndex = -1;
+    boolean mReorderingAllowed;
+
+    ArrayList<Runnable> mCommitRunnables;
+
+    int mBreadCrumbTitleRes;
+    CharSequence mBreadCrumbTitleText;
+    int mBreadCrumbShortTitleRes;
+    CharSequence mBreadCrumbShortTitleText;
+
+    ArrayList<String> mSharedElementSourceNames;
+    ArrayList<String> mSharedElementTargetNames;
+
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder(128);
+        sb.append("BackStackEntry{");
+        sb.append(Integer.toHexString(System.identityHashCode(this)));
+        if (mIndex >= 0) {
+            sb.append(" #");
+            sb.append(mIndex);
+        }
+        if (mName != null) {
+            sb.append(" ");
+            sb.append(mName);
+        }
+        sb.append("}");
+        return sb.toString();
+    }
+
+    public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+        dump(prefix, writer, true);
+    }
+
+    void dump(String prefix, PrintWriter writer, boolean full) {
+        if (full) {
+            writer.print(prefix);
+            writer.print("mName=");
+            writer.print(mName);
+            writer.print(" mIndex=");
+            writer.print(mIndex);
+            writer.print(" mCommitted=");
+            writer.println(mCommitted);
+            if (mTransition != FragmentTransaction.TRANSIT_NONE) {
+                writer.print(prefix);
+                writer.print("mTransition=#");
+                writer.print(Integer.toHexString(mTransition));
+                writer.print(" mTransitionStyle=#");
+                writer.println(Integer.toHexString(mTransitionStyle));
+            }
+            if (mEnterAnim != 0 || mExitAnim != 0) {
+                writer.print(prefix);
+                writer.print("mEnterAnim=#");
+                writer.print(Integer.toHexString(mEnterAnim));
+                writer.print(" mExitAnim=#");
+                writer.println(Integer.toHexString(mExitAnim));
+            }
+            if (mPopEnterAnim != 0 || mPopExitAnim != 0) {
+                writer.print(prefix);
+                writer.print("mPopEnterAnim=#");
+                writer.print(Integer.toHexString(mPopEnterAnim));
+                writer.print(" mPopExitAnim=#");
+                writer.println(Integer.toHexString(mPopExitAnim));
+            }
+            if (mBreadCrumbTitleRes != 0 || mBreadCrumbTitleText != null) {
+                writer.print(prefix);
+                writer.print("mBreadCrumbTitleRes=#");
+                writer.print(Integer.toHexString(mBreadCrumbTitleRes));
+                writer.print(" mBreadCrumbTitleText=");
+                writer.println(mBreadCrumbTitleText);
+            }
+            if (mBreadCrumbShortTitleRes != 0 || mBreadCrumbShortTitleText != null) {
+                writer.print(prefix);
+                writer.print("mBreadCrumbShortTitleRes=#");
+                writer.print(Integer.toHexString(mBreadCrumbShortTitleRes));
+                writer.print(" mBreadCrumbShortTitleText=");
+                writer.println(mBreadCrumbShortTitleText);
+            }
+        }
+
+        if (!mOps.isEmpty()) {
+            writer.print(prefix);
+            writer.println("Operations:");
+            String innerPrefix = prefix + "    ";
+            final int numOps = mOps.size();
+            for (int opNum = 0; opNum < numOps; opNum++) {
+                final Op op = mOps.get(opNum);
+                String cmdStr;
+                switch (op.cmd) {
+                    case OP_NULL:
+                        cmdStr = "NULL";
+                        break;
+                    case OP_ADD:
+                        cmdStr = "ADD";
+                        break;
+                    case OP_REPLACE:
+                        cmdStr = "REPLACE";
+                        break;
+                    case OP_REMOVE:
+                        cmdStr = "REMOVE";
+                        break;
+                    case OP_HIDE:
+                        cmdStr = "HIDE";
+                        break;
+                    case OP_SHOW:
+                        cmdStr = "SHOW";
+                        break;
+                    case OP_DETACH:
+                        cmdStr = "DETACH";
+                        break;
+                    case OP_ATTACH:
+                        cmdStr = "ATTACH";
+                        break;
+                    case OP_SET_PRIMARY_NAV:
+                        cmdStr="SET_PRIMARY_NAV";
+                        break;
+                    case OP_UNSET_PRIMARY_NAV:
+                        cmdStr="UNSET_PRIMARY_NAV";
+                        break;
+
+                    default:
+                        cmdStr = "cmd=" + op.cmd;
+                        break;
+                }
+                writer.print(prefix);
+                writer.print("  Op #");
+                writer.print(opNum);
+                writer.print(": ");
+                writer.print(cmdStr);
+                writer.print(" ");
+                writer.println(op.fragment);
+                if (full) {
+                    if (op.enterAnim != 0 || op.exitAnim != 0) {
+                        writer.print(innerPrefix);
+                        writer.print("enterAnim=#");
+                        writer.print(Integer.toHexString(op.enterAnim));
+                        writer.print(" exitAnim=#");
+                        writer.println(Integer.toHexString(op.exitAnim));
+                    }
+                    if (op.popEnterAnim != 0 || op.popExitAnim != 0) {
+                        writer.print(innerPrefix);
+                        writer.print("popEnterAnim=#");
+                        writer.print(Integer.toHexString(op.popEnterAnim));
+                        writer.print(" popExitAnim=#");
+                        writer.println(Integer.toHexString(op.popExitAnim));
+                    }
+                }
+            }
+        }
+    }
+
+    public BackStackRecord(FragmentManagerImpl manager) {
+        mManager = manager;
+        mReorderingAllowed = mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1;
+    }
+
+    public int getId() {
+        return mIndex;
+    }
+
+    public int getBreadCrumbTitleRes() {
+        return mBreadCrumbTitleRes;
+    }
+
+    public int getBreadCrumbShortTitleRes() {
+        return mBreadCrumbShortTitleRes;
+    }
+
+    public CharSequence getBreadCrumbTitle() {
+        if (mBreadCrumbTitleRes != 0 && mManager.mHost != null) {
+            return mManager.mHost.getContext().getText(mBreadCrumbTitleRes);
+        }
+        return mBreadCrumbTitleText;
+    }
+
+    public CharSequence getBreadCrumbShortTitle() {
+        if (mBreadCrumbShortTitleRes != 0 && mManager.mHost != null) {
+            return mManager.mHost.getContext().getText(mBreadCrumbShortTitleRes);
+        }
+        return mBreadCrumbShortTitleText;
+    }
+
+    void addOp(Op op) {
+        mOps.add(op);
+        op.enterAnim = mEnterAnim;
+        op.exitAnim = mExitAnim;
+        op.popEnterAnim = mPopEnterAnim;
+        op.popExitAnim = mPopExitAnim;
+    }
+
+    public FragmentTransaction add(Fragment fragment, String tag) {
+        doAddOp(0, fragment, tag, OP_ADD);
+        return this;
+    }
+
+    public FragmentTransaction add(int containerViewId, Fragment fragment) {
+        doAddOp(containerViewId, fragment, null, OP_ADD);
+        return this;
+    }
+
+    public FragmentTransaction add(int containerViewId, Fragment fragment, String tag) {
+        doAddOp(containerViewId, fragment, tag, OP_ADD);
+        return this;
+    }
+
+    private void doAddOp(int containerViewId, Fragment fragment, String tag, int opcmd) {
+        if (mManager.getTargetSdk() > Build.VERSION_CODES.N_MR1) {
+            final Class fragmentClass = fragment.getClass();
+            final int modifiers = fragmentClass.getModifiers();
+            if ((fragmentClass.isAnonymousClass() || !Modifier.isPublic(modifiers)
+                    || (fragmentClass.isMemberClass() && !Modifier.isStatic(modifiers)))) {
+                throw new IllegalStateException("Fragment " + fragmentClass.getCanonicalName()
+                        + " must be a public static class to be  properly recreated from"
+                        + " instance state.");
+            }
+        }
+        fragment.mFragmentManager = mManager;
+
+        if (tag != null) {
+            if (fragment.mTag != null && !tag.equals(fragment.mTag)) {
+                throw new IllegalStateException("Can't change tag of fragment "
+                        + fragment + ": was " + fragment.mTag
+                        + " now " + tag);
+            }
+            fragment.mTag = tag;
+        }
+
+        if (containerViewId != 0) {
+            if (containerViewId == View.NO_ID) {
+                throw new IllegalArgumentException("Can't add fragment "
+                        + fragment + " with tag " + tag + " to container view with no id");
+            }
+            if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
+                throw new IllegalStateException("Can't change container ID of fragment "
+                        + fragment + ": was " + fragment.mFragmentId
+                        + " now " + containerViewId);
+            }
+            fragment.mContainerId = fragment.mFragmentId = containerViewId;
+        }
+
+        addOp(new Op(opcmd, fragment));
+    }
+
+    public FragmentTransaction replace(int containerViewId, Fragment fragment) {
+        return replace(containerViewId, fragment, null);
+    }
+
+    public FragmentTransaction replace(int containerViewId, Fragment fragment, String tag) {
+        if (containerViewId == 0) {
+            throw new IllegalArgumentException("Must use non-zero containerViewId");
+        }
+
+        doAddOp(containerViewId, fragment, tag, OP_REPLACE);
+        return this;
+    }
+
+    public FragmentTransaction remove(Fragment fragment) {
+        addOp(new Op(OP_REMOVE, fragment));
+
+        return this;
+    }
+
+    public FragmentTransaction hide(Fragment fragment) {
+        addOp(new Op(OP_HIDE, fragment));
+
+        return this;
+    }
+
+    public FragmentTransaction show(Fragment fragment) {
+        addOp(new Op(OP_SHOW, fragment));
+
+        return this;
+    }
+
+    public FragmentTransaction detach(Fragment fragment) {
+        addOp(new Op(OP_DETACH, fragment));
+
+        return this;
+    }
+
+    public FragmentTransaction attach(Fragment fragment) {
+        addOp(new Op(OP_ATTACH, fragment));
+
+        return this;
+    }
+
+    public FragmentTransaction setPrimaryNavigationFragment(Fragment fragment) {
+        addOp(new Op(OP_SET_PRIMARY_NAV, fragment));
+
+        return this;
+    }
+
+    public FragmentTransaction setCustomAnimations(int enter, int exit) {
+        return setCustomAnimations(enter, exit, 0, 0);
+    }
+
+    public FragmentTransaction setCustomAnimations(int enter, int exit,
+            int popEnter, int popExit) {
+        mEnterAnim = enter;
+        mExitAnim = exit;
+        mPopEnterAnim = popEnter;
+        mPopExitAnim = popExit;
+        return this;
+    }
+
+    public FragmentTransaction setTransition(int transition) {
+        mTransition = transition;
+        return this;
+    }
+
+    @Override
+    public FragmentTransaction addSharedElement(View sharedElement, String name) {
+        String transitionName = sharedElement.getTransitionName();
+        if (transitionName == null) {
+            throw new IllegalArgumentException("Unique transitionNames are required for all" +
+                    " sharedElements");
+        }
+        if (mSharedElementSourceNames == null) {
+            mSharedElementSourceNames = new ArrayList<String>();
+            mSharedElementTargetNames = new ArrayList<String>();
+        } else if (mSharedElementTargetNames.contains(name)) {
+            throw new IllegalArgumentException("A shared element with the target name '"
+                    + name + "' has already been added to the transaction.");
+        } else if (mSharedElementSourceNames.contains(transitionName)) {
+            throw new IllegalArgumentException("A shared element with the source name '"
+                    + transitionName + " has already been added to the transaction.");
+        }
+        mSharedElementSourceNames.add(transitionName);
+        mSharedElementTargetNames.add(name);
+        return this;
+    }
+
+    public FragmentTransaction setTransitionStyle(int styleRes) {
+        mTransitionStyle = styleRes;
+        return this;
+    }
+
+    public FragmentTransaction addToBackStack(String name) {
+        if (!mAllowAddToBackStack) {
+            throw new IllegalStateException(
+                    "This FragmentTransaction is not allowed to be added to the back stack.");
+        }
+        mAddToBackStack = true;
+        mName = name;
+        return this;
+    }
+
+    public boolean isAddToBackStackAllowed() {
+        return mAllowAddToBackStack;
+    }
+
+    public FragmentTransaction disallowAddToBackStack() {
+        if (mAddToBackStack) {
+            throw new IllegalStateException(
+                    "This transaction is already being added to the back stack");
+        }
+        mAllowAddToBackStack = false;
+        return this;
+    }
+
+    public FragmentTransaction setBreadCrumbTitle(int res) {
+        mBreadCrumbTitleRes = res;
+        mBreadCrumbTitleText = null;
+        return this;
+    }
+
+    public FragmentTransaction setBreadCrumbTitle(CharSequence text) {
+        mBreadCrumbTitleRes = 0;
+        mBreadCrumbTitleText = text;
+        return this;
+    }
+
+    public FragmentTransaction setBreadCrumbShortTitle(int res) {
+        mBreadCrumbShortTitleRes = res;
+        mBreadCrumbShortTitleText = null;
+        return this;
+    }
+
+    public FragmentTransaction setBreadCrumbShortTitle(CharSequence text) {
+        mBreadCrumbShortTitleRes = 0;
+        mBreadCrumbShortTitleText = text;
+        return this;
+    }
+
+    void bumpBackStackNesting(int amt) {
+        if (!mAddToBackStack) {
+            return;
+        }
+        if (FragmentManagerImpl.DEBUG) {
+            Log.v(TAG, "Bump nesting in " + this
+                    + " by " + amt);
+        }
+        final int numOps = mOps.size();
+        for (int opNum = 0; opNum < numOps; opNum++) {
+            final Op op = mOps.get(opNum);
+            if (op.fragment != null) {
+                op.fragment.mBackStackNesting += amt;
+                if (FragmentManagerImpl.DEBUG) {
+                    Log.v(TAG, "Bump nesting of "
+                            + op.fragment + " to " + op.fragment.mBackStackNesting);
+                }
+            }
+        }
+    }
+
+    @Override
+    public FragmentTransaction runOnCommit(Runnable runnable) {
+        if (runnable == null) {
+            throw new IllegalArgumentException("runnable cannot be null");
+        }
+        disallowAddToBackStack();
+        if (mCommitRunnables == null) {
+            mCommitRunnables = new ArrayList<>();
+        }
+        mCommitRunnables.add(runnable);
+        return this;
+    }
+
+    public void runOnCommitRunnables() {
+        if (mCommitRunnables != null) {
+            for (int i = 0, N = mCommitRunnables.size(); i < N; i++) {
+                mCommitRunnables.get(i).run();
+            }
+            mCommitRunnables = null;
+        }
+    }
+
+    public int commit() {
+        return commitInternal(false);
+    }
+
+    public int commitAllowingStateLoss() {
+        return commitInternal(true);
+    }
+
+    @Override
+    public void commitNow() {
+        disallowAddToBackStack();
+        mManager.execSingleAction(this, false);
+    }
+
+    @Override
+    public void commitNowAllowingStateLoss() {
+        disallowAddToBackStack();
+        mManager.execSingleAction(this, true);
+    }
+
+    @Override
+    public FragmentTransaction setReorderingAllowed(boolean reorderingAllowed) {
+        mReorderingAllowed = reorderingAllowed;
+        return this;
+    }
+
+    int commitInternal(boolean allowStateLoss) {
+        if (mCommitted) {
+            throw new IllegalStateException("commit already called");
+        }
+        if (FragmentManagerImpl.DEBUG) {
+            Log.v(TAG, "Commit: " + this);
+            LogWriter logw = new LogWriter(Log.VERBOSE, TAG);
+            PrintWriter pw = new FastPrintWriter(logw, false, 1024);
+            dump("  ", null, pw, null);
+            pw.flush();
+        }
+        mCommitted = true;
+        if (mAddToBackStack) {
+            mIndex = mManager.allocBackStackIndex(this);
+        } else {