Automatic sources dropoff on 2020-06-10 18:32:38.095721

The change is generated with prebuilt drop tool.

Change-Id: I24cbf6ba6db262a1ae1445db1427a08fee35b3b4
diff --git a/.prebuilt_info/AndroidX/prebuilt_info_sdk-repo-linux-sources-*_zip.asciipb b/.prebuilt_info/AndroidX/prebuilt_info_sdk-repo-linux-sources-*_zip.asciipb
new file mode 100644
index 0000000..75aeb2a
--- /dev/null
+++ b/.prebuilt_info/AndroidX/prebuilt_info_sdk-repo-linux-sources-*_zip.asciipb
@@ -0,0 +1,12 @@
+drops {
+  android_build_drop {
+    build_id: "6520161"
+    target: "sdk_phone_x86_64-sdk"
+    source_file: "sdk-repo-linux-sources-6520161.zip"
+  }
+  dest_file: "sdk-repo-linux-sources-*.zip"
+  version: ""
+  version_group: "AndroidX"
+  git_project: "platform/prebuilts/fullsdk/sources/android-30"
+  git_branch: "master"
+}
diff --git a/android/accessibilityservice/AccessibilityButtonController.java b/android/accessibilityservice/AccessibilityButtonController.java
new file mode 100644
index 0000000..2ccad1d
--- /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 java.util.Objects;
+
+/**
+ * Controller for the accessibility button within the system's navigation area
+ * <p>
+ * This class may be used to query the accessibility button's state and register
+ * callbacks for interactions with and state changes to the accessibility button when
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> This class and
+ * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} should not be used as
+ * the sole means for offering functionality to users via an {@link AccessibilityService}.
+ * Some device implementations may choose not to provide a software-rendered system
+ * navigation area, making this affordance permanently unavailable.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> On device implementations where the accessibility button is
+ * supported, it may not be available at all times, such as when a foreground application uses
+ * {@link android.view.View#SYSTEM_UI_FLAG_HIDE_NAVIGATION}. A user may also choose to assign
+ * this button to another accessibility service or feature. In each of these cases, a
+ * registered {@link AccessibilityButtonCallback}'s
+ * {@link AccessibilityButtonCallback#onAvailabilityChanged(AccessibilityButtonController, boolean)}
+ * method will be invoked to provide notifications of changes in the accessibility button's
+ * availability to the registering service.
+ * </p>
+ */
+public final class AccessibilityButtonController {
+    private static final String LOG_TAG = "A11yButtonController";
+
+    private final IAccessibilityServiceConnection mServiceConnection;
+    private final Object mLock;
+    private ArrayMap<AccessibilityButtonCallback, Handler> mCallbacks;
+
+    AccessibilityButtonController(@NonNull IAccessibilityServiceConnection serviceConnection) {
+        mServiceConnection = serviceConnection;
+        mLock = new Object();
+    }
+
+    /**
+     * Retrieves whether the accessibility button in the system's navigation area is
+     * available to the calling service.
+     * <p>
+     * <strong>Note:</strong> If the service is not yet connected (e.g.
+     * {@link AccessibilityService#onServiceConnected()} has not yet been called) or the
+     * service has been disconnected, this method will have no effect and return {@code false}.
+     * </p>
+     *
+     * @return {@code true} if the accessibility button in the system's navigation area is
+     * available to the calling service, {@code false} otherwise
+     */
+    public boolean isAccessibilityButtonAvailable() {
+        if (mServiceConnection != null) {
+            try {
+                return mServiceConnection.isAccessibilityButtonAvailable();
+            } catch (RemoteException re) {
+                Slog.w(LOG_TAG, "Failed to get accessibility button availability.", re);
+                re.rethrowFromSystemServer();
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
+     * changes callbacks related to the accessibility button.
+     *
+     * @param callback the callback to add, must be non-null
+     */
+    public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback) {
+        registerAccessibilityButtonCallback(callback, new Handler(Looper.getMainLooper()));
+    }
+
+    /**
+     * Registers the provided {@link AccessibilityButtonCallback} for interaction and state
+     * change callbacks related to the accessibility button. The callback will occur on the
+     * specified {@link Handler}'s thread, or on the services's main thread if the handler is
+     * {@code null}.
+     *
+     * @param callback the callback to add, must be non-null
+     * @param handler the handler on which the callback should execute, must be non-null
+     */
+    public void registerAccessibilityButtonCallback(@NonNull AccessibilityButtonCallback callback,
+            @NonNull Handler handler) {
+        Objects.requireNonNull(callback);
+        Objects.requireNonNull(handler);
+        synchronized (mLock) {
+            if (mCallbacks == null) {
+                mCallbacks = new ArrayMap<>();
+            }
+
+            mCallbacks.put(callback, handler);
+        }
+    }
+
+    /**
+     * Unregisters the provided {@link AccessibilityButtonCallback} for interaction and state
+     * change callbacks related to the accessibility button.
+     *
+     * @param callback the callback to remove, must be non-null
+     */
+    public void unregisterAccessibilityButtonCallback(
+            @NonNull AccessibilityButtonCallback callback) {
+        Objects.requireNonNull(callback);
+        synchronized (mLock) {
+            if (mCallbacks == null) {
+                return;
+            }
+
+            final int keyIndex = mCallbacks.indexOfKey(callback);
+            final boolean hasKey = keyIndex >= 0;
+            if (hasKey) {
+                mCallbacks.removeAt(keyIndex);
+            }
+        }
+    }
+
+    /**
+     * Dispatches the accessibility button click to any registered callbacks. This should
+     * be called on the service's main thread.
+     */
+    void dispatchAccessibilityButtonClicked() {
+        final ArrayMap<AccessibilityButtonCallback, Handler> entries;
+        synchronized (mLock) {
+            if (mCallbacks == null || mCallbacks.isEmpty()) {
+                Slog.w(LOG_TAG, "Received accessibility button click with no callbacks!");
+                return;
+            }
+
+            // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+            // modification.
+            entries = new ArrayMap<>(mCallbacks);
+        }
+
+        for (int i = 0, count = entries.size(); i < count; i++) {
+            final AccessibilityButtonCallback callback = entries.keyAt(i);
+            final Handler handler = entries.valueAt(i);
+            handler.post(() -> callback.onClicked(this));
+        }
+    }
+
+    /**
+     * Dispatches the accessibility button availability changes to any registered callbacks.
+     * This should be called on the service's main thread.
+     */
+    void dispatchAccessibilityButtonAvailabilityChanged(boolean available) {
+        final ArrayMap<AccessibilityButtonCallback, Handler> entries;
+        synchronized (mLock) {
+            if (mCallbacks == null || mCallbacks.isEmpty()) {
+                Slog.w(LOG_TAG,
+                        "Received accessibility button availability change with no callbacks!");
+                return;
+            }
+
+            // Callbacks may remove themselves. Perform a shallow copy to avoid concurrent
+            // modification.
+            entries = new ArrayMap<>(mCallbacks);
+        }
+
+        for (int i = 0, count = entries.size(); i < count; i++) {
+            final AccessibilityButtonCallback callback = entries.keyAt(i);
+            final Handler handler = entries.valueAt(i);
+            handler.post(() -> callback.onAvailabilityChanged(this, available));
+        }
+    }
+
+    /**
+     * Callback for interaction with and changes to state of the accessibility button
+     * within the system's navigation area.
+     */
+    public static abstract class AccessibilityButtonCallback {
+
+        /**
+         * Called when the accessibility button in the system's navigation area is clicked.
+         *
+         * @param controller the controller used to register for this callback
+         */
+        public void onClicked(AccessibilityButtonController controller) {}
+
+        /**
+         * Called when the availability of the accessibility button in the system's
+         * navigation area has changed. The accessibility button may become unavailable
+         * because the device shopped showing the button, the button was assigned to another
+         * service, or for other reasons.
+         *
+         * @param controller the controller used to register for this callback
+         * @param available {@code true} if the accessibility button is available to this
+         *                  service, {@code false} otherwise
+         */
+        public void onAvailabilityChanged(AccessibilityButtonController controller,
+                boolean available) {
+        }
+    }
+}
diff --git a/android/accessibilityservice/AccessibilityGestureEvent.java b/android/accessibilityservice/AccessibilityGestureEvent.java
new file mode 100644
index 0000000..25729ab
--- /dev/null
+++ b/android/accessibilityservice/AccessibilityGestureEvent.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice;
+
+
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_2_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_3_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SINGLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_4_FINGER_TRIPLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_DOUBLE_TAP_AND_HOLD;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_DOWN_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_LEFT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_RIGHT_AND_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_DOWN;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_LEFT;
+import static android.accessibilityservice.AccessibilityService.GESTURE_SWIPE_UP_AND_RIGHT;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.TestApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * This class describes the gesture event including gesture id and which display it happens
+ * on.
+ * <p>
+ * <strong>Note:</strong> Accessibility services setting the
+ * {@link android.accessibilityservice.AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+ * flag can receive gestures.
+ *
+ * @see AccessibilityService#onGesture(AccessibilityGestureEvent)
+ */
+
+public final class AccessibilityGestureEvent implements Parcelable {
+
+    /** @hide */
+    @IntDef(prefix = { "GESTURE_" }, value = {
+            GESTURE_2_FINGER_SINGLE_TAP,
+            GESTURE_2_FINGER_DOUBLE_TAP,
+            GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD,
+            GESTURE_2_FINGER_TRIPLE_TAP,
+            GESTURE_3_FINGER_SINGLE_TAP,
+            GESTURE_3_FINGER_DOUBLE_TAP,
+            GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD,
+            GESTURE_3_FINGER_TRIPLE_TAP,
+            GESTURE_DOUBLE_TAP,
+            GESTURE_DOUBLE_TAP_AND_HOLD,
+            GESTURE_SWIPE_UP,
+            GESTURE_SWIPE_UP_AND_LEFT,
+            GESTURE_SWIPE_UP_AND_DOWN,
+            GESTURE_SWIPE_UP_AND_RIGHT,
+            GESTURE_SWIPE_DOWN,
+            GESTURE_SWIPE_DOWN_AND_LEFT,
+            GESTURE_SWIPE_DOWN_AND_UP,
+            GESTURE_SWIPE_DOWN_AND_RIGHT,
+            GESTURE_SWIPE_LEFT,
+            GESTURE_SWIPE_LEFT_AND_UP,
+            GESTURE_SWIPE_LEFT_AND_RIGHT,
+            GESTURE_SWIPE_LEFT_AND_DOWN,
+            GESTURE_SWIPE_RIGHT,
+            GESTURE_SWIPE_RIGHT_AND_UP,
+            GESTURE_SWIPE_RIGHT_AND_LEFT,
+            GESTURE_SWIPE_RIGHT_AND_DOWN,
+            GESTURE_2_FINGER_SWIPE_DOWN,
+            GESTURE_2_FINGER_SWIPE_LEFT,
+            GESTURE_2_FINGER_SWIPE_RIGHT,
+            GESTURE_2_FINGER_SWIPE_UP,
+            GESTURE_3_FINGER_SWIPE_DOWN,
+            GESTURE_3_FINGER_SWIPE_LEFT,
+            GESTURE_3_FINGER_SWIPE_RIGHT,
+            GESTURE_3_FINGER_SWIPE_UP,
+            GESTURE_4_FINGER_DOUBLE_TAP,
+            GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD,
+            GESTURE_4_FINGER_SINGLE_TAP,
+            GESTURE_4_FINGER_SWIPE_DOWN,
+            GESTURE_4_FINGER_SWIPE_LEFT,
+            GESTURE_4_FINGER_SWIPE_RIGHT,
+            GESTURE_4_FINGER_SWIPE_UP,
+            GESTURE_4_FINGER_TRIPLE_TAP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface GestureId {}
+
+    @GestureId
+    private final int mGestureId;
+    private final int mDisplayId;
+
+    /** @hide */
+    @TestApi
+    public AccessibilityGestureEvent(int gestureId, int displayId) {
+        mGestureId = gestureId;
+        mDisplayId = displayId;
+    }
+
+    private AccessibilityGestureEvent(@NonNull Parcel parcel) {
+        mGestureId = parcel.readInt();
+        mDisplayId = parcel.readInt();
+    }
+
+    /**
+     * Returns the display id of the received-gesture display, for use with
+     * {@link android.hardware.display.DisplayManager#getDisplay(int)}.
+     *
+     * @return the display id.
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /**
+     * Returns performed gesture id.
+     *
+     * @return the performed gesture id.
+     *
+     */
+    @GestureId public int getGestureId() {
+        return mGestureId;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder("AccessibilityGestureEvent[");
+        stringBuilder.append("gestureId: ").append(eventTypeToString(mGestureId));
+        stringBuilder.append(", ");
+        stringBuilder.append("displayId: ").append(mDisplayId);
+        stringBuilder.append(']');
+        return stringBuilder.toString();
+    }
+
+    private static String eventTypeToString(int eventType) {
+        switch (eventType) {
+            case GESTURE_2_FINGER_SINGLE_TAP: return "GESTURE_2_FINGER_SINGLE_TAP";
+            case GESTURE_2_FINGER_DOUBLE_TAP: return "GESTURE_2_FINGER_DOUBLE_TAP";
+            case GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD:
+                return "GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD";
+            case GESTURE_2_FINGER_TRIPLE_TAP: return "GESTURE_2_FINGER_TRIPLE_TAP";
+            case GESTURE_3_FINGER_SINGLE_TAP: return "GESTURE_3_FINGER_SINGLE_TAP";
+            case GESTURE_3_FINGER_DOUBLE_TAP: return "GESTURE_3_FINGER_DOUBLE_TAP";
+            case GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD:
+                return "GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD";
+            case GESTURE_3_FINGER_TRIPLE_TAP: return "GESTURE_3_FINGER_TRIPLE_TAP";
+            case GESTURE_4_FINGER_SINGLE_TAP: return "GESTURE_4_FINGER_SINGLE_TAP";
+            case GESTURE_4_FINGER_DOUBLE_TAP: return "GESTURE_4_FINGER_DOUBLE_TAP";
+            case GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD:
+                return "GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD";
+            case GESTURE_4_FINGER_TRIPLE_TAP: return "GESTURE_4_FINGER_TRIPLE_TAP";
+            case GESTURE_DOUBLE_TAP: return "GESTURE_DOUBLE_TAP";
+            case GESTURE_DOUBLE_TAP_AND_HOLD: return "GESTURE_DOUBLE_TAP_AND_HOLD";
+            case GESTURE_SWIPE_DOWN: return "GESTURE_SWIPE_DOWN";
+            case GESTURE_SWIPE_DOWN_AND_LEFT: return "GESTURE_SWIPE_DOWN_AND_LEFT";
+            case GESTURE_SWIPE_DOWN_AND_UP: return "GESTURE_SWIPE_DOWN_AND_UP";
+            case GESTURE_SWIPE_DOWN_AND_RIGHT: return "GESTURE_SWIPE_DOWN_AND_RIGHT";
+            case GESTURE_SWIPE_LEFT: return "GESTURE_SWIPE_LEFT";
+            case GESTURE_SWIPE_LEFT_AND_UP: return "GESTURE_SWIPE_LEFT_AND_UP";
+            case GESTURE_SWIPE_LEFT_AND_RIGHT: return "GESTURE_SWIPE_LEFT_AND_RIGHT";
+            case GESTURE_SWIPE_LEFT_AND_DOWN: return "GESTURE_SWIPE_LEFT_AND_DOWN";
+            case GESTURE_SWIPE_RIGHT: return "GESTURE_SWIPE_RIGHT";
+            case GESTURE_SWIPE_RIGHT_AND_UP: return "GESTURE_SWIPE_RIGHT_AND_UP";
+            case GESTURE_SWIPE_RIGHT_AND_LEFT: return "GESTURE_SWIPE_RIGHT_AND_LEFT";
+            case GESTURE_SWIPE_RIGHT_AND_DOWN: return "GESTURE_SWIPE_RIGHT_AND_DOWN";
+            case GESTURE_SWIPE_UP: return "GESTURE_SWIPE_UP";
+            case GESTURE_SWIPE_UP_AND_LEFT: return "GESTURE_SWIPE_UP_AND_LEFT";
+            case GESTURE_SWIPE_UP_AND_DOWN: return "GESTURE_SWIPE_UP_AND_DOWN";
+            case GESTURE_SWIPE_UP_AND_RIGHT: return "GESTURE_SWIPE_UP_AND_RIGHT";
+            case GESTURE_2_FINGER_SWIPE_DOWN: return "GESTURE_2_FINGER_SWIPE_DOWN";
+            case GESTURE_2_FINGER_SWIPE_LEFT: return "GESTURE_2_FINGER_SWIPE_LEFT";
+            case GESTURE_2_FINGER_SWIPE_RIGHT: return "GESTURE_2_FINGER_SWIPE_RIGHT";
+            case GESTURE_2_FINGER_SWIPE_UP: return "GESTURE_2_FINGER_SWIPE_UP";
+            case GESTURE_3_FINGER_SWIPE_DOWN: return "GESTURE_3_FINGER_SWIPE_DOWN";
+            case GESTURE_3_FINGER_SWIPE_LEFT: return "GESTURE_3_FINGER_SWIPE_LEFT";
+            case GESTURE_3_FINGER_SWIPE_RIGHT: return "GESTURE_3_FINGER_SWIPE_RIGHT";
+            case GESTURE_3_FINGER_SWIPE_UP: return "GESTURE_3_FINGER_SWIPE_UP";
+            case GESTURE_4_FINGER_SWIPE_DOWN: return "GESTURE_4_FINGER_SWIPE_DOWN";
+            case GESTURE_4_FINGER_SWIPE_LEFT: return "GESTURE_4_FINGER_SWIPE_LEFT";
+            case GESTURE_4_FINGER_SWIPE_RIGHT: return "GESTURE_4_FINGER_SWIPE_RIGHT";
+            case GESTURE_4_FINGER_SWIPE_UP: return "GESTURE_4_FINGER_SWIPE_UP";
+            default: return Integer.toHexString(eventType);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mGestureId);
+        parcel.writeInt(mDisplayId);
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    public static final @NonNull Parcelable.Creator<AccessibilityGestureEvent> CREATOR =
+            new Parcelable.Creator<AccessibilityGestureEvent>() {
+        public AccessibilityGestureEvent createFromParcel(Parcel parcel) {
+            return new AccessibilityGestureEvent(parcel);
+        }
+
+        public AccessibilityGestureEvent[] newArray(int size) {
+            return new AccessibilityGestureEvent[size];
+        }
+    };
+
+}
diff --git a/android/accessibilityservice/AccessibilityService.java b/android/accessibilityservice/AccessibilityService.java
new file mode 100644
index 0000000..77b3c81
--- /dev/null
+++ b/android/accessibilityservice/AccessibilityService.java
@@ -0,0 +1,2573 @@
+/*
+ * 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.CallbackExecutor;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.TestApi;
+import android.app.Service;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ParceledListSlice;
+import android.graphics.Bitmap;
+import android.graphics.ColorSpace;
+import android.graphics.ParcelableColorSpace;
+import android.graphics.Region;
+import android.hardware.HardwareBuffer;
+import android.os.Build;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteCallback;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.view.Display;
+import android.view.KeyEvent;
+import android.view.SurfaceView;
+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.AccessibilityNodeInfo.AccessibilityAction;
+import android.view.accessibility.AccessibilityWindowInfo;
+
+import com.android.internal.os.HandlerCaller;
+import com.android.internal.os.SomeArgs;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * 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 user has performed a double tap gesture on the touch screen.
+     */
+    public static final int GESTURE_DOUBLE_TAP = 17;
+
+    /**
+     * The user has performed a double tap and hold gesture on the touch screen.
+     */
+    public static final int GESTURE_DOUBLE_TAP_AND_HOLD = 18;
+
+    /**
+     * The user has performed a two-finger single tap gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_SINGLE_TAP = 19;
+
+    /**
+     * The user has performed a two-finger double tap gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_DOUBLE_TAP = 20;
+
+    /**
+     * The user has performed a two-finger triple tap gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_TRIPLE_TAP = 21;
+
+    /**
+     * The user has performed a three-finger single tap gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_SINGLE_TAP = 22;
+
+    /**
+     * The user has performed a three-finger double tap gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_DOUBLE_TAP = 23;
+
+    /**
+     * The user has performed a three-finger triple tap gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_TRIPLE_TAP = 24;
+
+    /**
+     * The user has performed a two-finger swipe up gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_SWIPE_UP = 25;
+
+    /**
+     * The user has performed a two-finger swipe down gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_SWIPE_DOWN = 26;
+
+    /**
+     * The user has performed a two-finger swipe left gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_SWIPE_LEFT = 27;
+
+    /**
+     * The user has performed a two-finger swipe right gesture on the touch screen.
+     */
+    public static final int GESTURE_2_FINGER_SWIPE_RIGHT = 28;
+
+    /**
+     * The user has performed a three-finger swipe up gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_SWIPE_UP = 29;
+
+    /**
+     * The user has performed a three-finger swipe down gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_SWIPE_DOWN = 30;
+
+    /**
+     * The user has performed a three-finger swipe left gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_SWIPE_LEFT = 31;
+
+    /**
+     * The user has performed a three-finger swipe right gesture on the touch screen.
+     */
+    public static final int GESTURE_3_FINGER_SWIPE_RIGHT = 32;
+
+    /** The user has performed a four-finger swipe up gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_SWIPE_UP = 33;
+
+    /** The user has performed a four-finger swipe down gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_SWIPE_DOWN = 34;
+
+    /** The user has performed a four-finger swipe left gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_SWIPE_LEFT = 35;
+
+    /** The user has performed a four-finger swipe right gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_SWIPE_RIGHT = 36;
+
+    /** The user has performed a four-finger single tap gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_SINGLE_TAP = 37;
+
+    /** The user has performed a four-finger double tap gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_DOUBLE_TAP = 38;
+
+    /** The user has performed a four-finger triple tap gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_TRIPLE_TAP = 39;
+
+    /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+    public static final int GESTURE_2_FINGER_DOUBLE_TAP_AND_HOLD = 40;
+
+    /** The user has performed a three-finger double tap and hold gesture on the touch screen. */
+    public static final int GESTURE_3_FINGER_DOUBLE_TAP_AND_HOLD = 41;
+
+    /** The user has performed a two-finger double tap and hold gesture on the touch screen. */
+    public static final int GESTURE_4_FINGER_DOUBLE_TAP_AND_HOLD = 42;
+
+    /**
+     * The {@link Intent} that must be declared as handled by the service.
+     */
+    public static final String SERVICE_INTERFACE =
+        "android.accessibilityservice.AccessibilityService";
+
+    /**
+     * Name under which an AccessibilityService component publishes information
+     * about itself. This meta-data must reference an XML resource containing an
+     * <code>&lt;{@link android.R.styleable#AccessibilityService accessibility-service}&gt;</code>
+     * tag. This is a sample XML file configuring an accessibility service:
+     * <pre> &lt;accessibility-service
+     *     android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
+     *     android:packageNames="foo.bar, foo.baz"
+     *     android:accessibilityFeedbackType="feedbackSpoken"
+     *     android:notificationTimeout="100"
+     *     android:accessibilityFlags="flagDefault"
+     *     android:settingsActivity="foo.bar.TestBackActivity"
+     *     android:canRetrieveWindowContent="true"
+     *     android:canRequestTouchExplorationMode="true"
+     *     . . .
+     * /&gt;</pre>
+     */
+    public static final String SERVICE_META_DATA = "android.accessibilityservice";
+
+    /**
+     * Action to go back.
+     */
+    public static final int GLOBAL_ACTION_BACK = 1;
+
+    /**
+     * Action to go home.
+     */
+    public static final int GLOBAL_ACTION_HOME = 2;
+
+    /**
+     * Action to toggle showing the overview of recent apps. Will fail on platforms that don't
+     * show recent apps.
+     */
+    public static final int GLOBAL_ACTION_RECENTS = 3;
+
+    /**
+     * Action to open the notifications.
+     */
+    public static final int GLOBAL_ACTION_NOTIFICATIONS = 4;
+
+    /**
+     * Action to open the quick settings.
+     */
+    public static final int GLOBAL_ACTION_QUICK_SETTINGS = 5;
+
+    /**
+     * Action to open the power long-press dialog.
+     */
+    public static final int GLOBAL_ACTION_POWER_DIALOG = 6;
+
+    /**
+     * Action to toggle docking the current app's window
+     */
+    public static final int GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN = 7;
+
+    /**
+     * Action to lock the screen
+     */
+    public static final int GLOBAL_ACTION_LOCK_SCREEN = 8;
+
+    /**
+     * Action to take a screenshot
+     */
+    public static final int GLOBAL_ACTION_TAKE_SCREENSHOT = 9;
+
+    /**
+     * Action to send the KEYCODE_HEADSETHOOK KeyEvent, which is used to answer/hang up calls and
+     * play/stop media
+     * @hide
+     */
+    public static final int GLOBAL_ACTION_KEYCODE_HEADSETHOOK = 10;
+
+    /**
+     * Action to trigger the Accessibility Button
+     * @hide
+     */
+    public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON = 11;
+
+    /**
+     * Action to bring up the Accessibility Button's chooser menu
+     * @hide
+     */
+    public static final int GLOBAL_ACTION_ACCESSIBILITY_BUTTON_CHOOSER = 12;
+
+    private static final String LOG_TAG = "AccessibilityService";
+
+    /**
+     * Interface used by IAccessibilityServiceClientWrapper to call the service from its main
+     * thread.
+     * @hide
+     */
+    public interface Callbacks {
+        void onAccessibilityEvent(AccessibilityEvent event);
+        void onInterrupt();
+        void onServiceConnected();
+        void init(int connectionId, IBinder windowToken);
+        /** The detected gesture information for different displays */
+        boolean onGesture(AccessibilityGestureEvent gestureInfo);
+        boolean onKeyEvent(KeyEvent event);
+        /** Magnification changed callbacks for different displays */
+        void onMagnificationChanged(int displayId, @NonNull Region region,
+                float scale, float centerX, float centerY);
+        void onSoftKeyboardShowModeChanged(int showMode);
+        void onPerformGestureResult(int sequence, boolean completedSuccessfully);
+        void onFingerprintCapturingGesturesChanged(boolean active);
+        void onFingerprintGesture(int gesture);
+        /** Accessbility button clicked callbacks for different displays */
+        void onAccessibilityButtonClicked(int displayId);
+        void onAccessibilityButtonAvailabilityChanged(boolean available);
+        /** This is called when the system action list is changed. */
+        void onSystemActionsChanged();
+    }
+
+    /**
+     * Annotations for Soft Keyboard show modes so tools can catch invalid show modes.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "SHOW_MODE_" }, value = {
+            SHOW_MODE_AUTO,
+            SHOW_MODE_HIDDEN,
+            SHOW_MODE_IGNORE_HARD_KEYBOARD
+    })
+    public @interface SoftKeyboardShowMode {}
+
+    /**
+     * Allow the system to control when the soft keyboard is shown.
+     * @see SoftKeyboardController
+     */
+    public static final int SHOW_MODE_AUTO = 0;
+
+    /**
+     * Never show the soft keyboard.
+     * @see SoftKeyboardController
+     */
+    public static final int SHOW_MODE_HIDDEN = 1;
+
+    /**
+     * Allow the soft keyboard to be shown, even if a hard keyboard is connected
+     * @see SoftKeyboardController
+     */
+    public static final int SHOW_MODE_IGNORE_HARD_KEYBOARD = 2;
+
+    /**
+     * Mask used to cover the show modes supported in public API
+     * @hide
+     */
+    public static final int SHOW_MODE_MASK = 0x03;
+
+    /**
+     * Bit used to hold the old value of the hard IME setting to restore when a service is shut
+     * down.
+     * @hide
+     */
+    public static final int SHOW_MODE_HARD_KEYBOARD_ORIGINAL_VALUE = 0x20000000;
+
+    /**
+     * Bit for show mode setting to indicate that the user has overridden the hard keyboard
+     * behavior.
+     * @hide
+     */
+    public static final int SHOW_MODE_HARD_KEYBOARD_OVERRIDDEN = 0x40000000;
+
+    /**
+     * Annotations for error codes of taking screenshot.
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = { "TAKE_SCREENSHOT_" }, value = {
+            ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR,
+            ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS,
+            ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT,
+            ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY
+    })
+    public @interface ScreenshotErrorCode {}
+
+    /**
+     * The status of taking screenshot is success.
+     * @hide
+     */
+    public static final int TAKE_SCREENSHOT_SUCCESS = 0;
+
+    /**
+     * The status of taking screenshot is failure and the reason is internal error.
+     */
+    public static final int ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR = 1;
+
+    /**
+     * The status of taking screenshot is failure and the reason is no accessibility access.
+     */
+    public static final int ERROR_TAKE_SCREENSHOT_NO_ACCESSIBILITY_ACCESS = 2;
+
+    /**
+     * The status of taking screenshot is failure and the reason is that too little time has
+     * elapsed since the last screenshot.
+     */
+    public static final int ERROR_TAKE_SCREENSHOT_INTERVAL_TIME_SHORT = 3;
+
+    /**
+     * The status of taking screenshot is failure and the reason is invalid display Id.
+     */
+    public static final int ERROR_TAKE_SCREENSHOT_INVALID_DISPLAY = 4;
+
+    /**
+     * The interval time of calling
+     * {@link AccessibilityService#takeScreenshot(int, Executor, Consumer)} API.
+     * @hide
+     */
+    @TestApi
+    public static final int ACCESSIBILITY_TAKE_SCREENSHOT_REQUEST_INTERVAL_TIMES_MS = 1000;
+
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_STATUS =
+            "screenshot_status";
+
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER =
+            "screenshot_hardwareBuffer";
+
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE =
+            "screenshot_colorSpace";
+
+    /** @hide */
+    public static final String KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP =
+            "screenshot_timestamp";
+
+    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 final SparseArray<AccessibilityButtonController> mAccessibilityButtonControllers =
+            new SparseArray<>(0);
+
+    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 {@link #onGesture(AccessibilityGestureEvent)} when the user performs a specific
+     * gesture on the default display.
+     *
+     * <strong>Note:</strong> To receive gestures an accessibility service must
+     * request that the device is in touch exploration mode by setting the
+     * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+     * flag.
+     *
+     * @param gestureId The unique id of the performed gesture.
+     *
+     * @return Whether the gesture was handled.
+     * @deprecated Override {@link #onGesture(AccessibilityGestureEvent)} instead.
+     *
+     * @see #GESTURE_SWIPE_UP
+     * @see #GESTURE_SWIPE_UP_AND_LEFT
+     * @see #GESTURE_SWIPE_UP_AND_DOWN
+     * @see #GESTURE_SWIPE_UP_AND_RIGHT
+     * @see #GESTURE_SWIPE_DOWN
+     * @see #GESTURE_SWIPE_DOWN_AND_LEFT
+     * @see #GESTURE_SWIPE_DOWN_AND_UP
+     * @see #GESTURE_SWIPE_DOWN_AND_RIGHT
+     * @see #GESTURE_SWIPE_LEFT
+     * @see #GESTURE_SWIPE_LEFT_AND_UP
+     * @see #GESTURE_SWIPE_LEFT_AND_RIGHT
+     * @see #GESTURE_SWIPE_LEFT_AND_DOWN
+     * @see #GESTURE_SWIPE_RIGHT
+     * @see #GESTURE_SWIPE_RIGHT_AND_UP
+     * @see #GESTURE_SWIPE_RIGHT_AND_LEFT
+     * @see #GESTURE_SWIPE_RIGHT_AND_DOWN
+     */
+    @Deprecated
+    protected boolean onGesture(int gestureId) {
+        return false;
+    }
+
+    /**
+     * Called by the system when the user performs a specific gesture on the
+     * specific touch screen.
+     *<p>
+     * <strong>Note:</strong> To receive gestures an accessibility service must
+     * request that the device is in touch exploration mode by setting the
+     * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE}
+     * flag.
+     *<p>
+     * <strong>Note:</strong> The default implementation calls {@link #onGesture(int)} when the
+     * touch screen is default display.
+     *
+     * @param gestureEvent The information of gesture.
+     *
+     * @return Whether the gesture was handled.
+     *
+     */
+    public boolean onGesture(@NonNull AccessibilityGestureEvent gestureEvent) {
+        if (gestureEvent.getDisplayId() == Display.DEFAULT_DISPLAY) {
+            onGesture(gestureEvent.getGestureId());
+        }
+        return false;
+    }
+
+    /**
+     * Callback that allows an accessibility service to observe the key events
+     * before they are passed to the rest of the system. This means that the events
+     * are first delivered here before they are passed to the device policy, the
+     * input method, or applications.
+     * <p>
+     * <strong>Note:</strong> It is important that key events are handled in such
+     * a way that the event stream that would be passed to the rest of the system
+     * is well-formed. For example, handling the down event but not the up event
+     * and vice versa would generate an inconsistent event stream.
+     * </p>
+     * <p>
+     * <strong>Note:</strong> The key events delivered in this method are copies
+     * and modifying them will have no effect on the events that will be passed
+     * to the system. This method is intended to perform purely filtering
+     * functionality.
+     * <p>
+     *
+     * @param event The event to be processed. This event is owned by the caller and cannot be used
+     * after this method returns. Services wishing to use the event after this method returns should
+     * make a copy.
+     * @return If true then the event will be consumed and not delivered to
+     *         applications, otherwise it will be delivered as usual.
+     */
+    protected boolean onKeyEvent(KeyEvent event) {
+        return false;
+    }
+
+    /**
+     * Gets the windows on the screen of the default display. This method returns only the windows
+     * that a sighted user can interact with, as opposed to all windows.
+     * For example, if there is a modal dialog shown and the user cannot touch
+     * anything behind it, then only the modal window will be reported
+     * (assuming it is the top one). For convenience the returned windows
+     * are ordered in a descending layer order, which is the windows that
+     * are on top are reported first. Since the user can always
+     * interact with the window that has input focus by typing, the focused
+     * window is always returned (even if covered by a modal window).
+     * <p>
+     * <strong>Note:</strong> In order to access the windows your service has
+     * to declare the capability to retrieve window content by setting the
+     * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+     * Also the service has to opt-in to retrieve the interactive windows by
+     * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
+     * flag.
+     * </p>
+     *
+     * @return The windows if there are windows and the service is can retrieve
+     *         them, otherwise an empty list.
+     */
+    public List<AccessibilityWindowInfo> getWindows() {
+        return AccessibilityInteractionClient.getInstance().getWindows(mConnectionId);
+    }
+
+    /**
+     * Gets the windows on the screen of all displays. This method returns only the windows
+     * that a sighted user can interact with, as opposed to all windows.
+     * For example, if there is a modal dialog shown and the user cannot touch
+     * anything behind it, then only the modal window will be reported
+     * (assuming it is the top one). For convenience the returned windows
+     * are ordered in a descending layer order, which is the windows that
+     * are on top are reported first. Since the user can always
+     * interact with the window that has input focus by typing, the focused
+     * window is always returned (even if covered by a modal window).
+     * <p>
+     * <strong>Note:</strong> In order to access the windows your service has
+     * to declare the capability to retrieve window content by setting the
+     * {@link android.R.styleable#AccessibilityService_canRetrieveWindowContent}
+     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+     * Also the service has to opt-in to retrieve the interactive windows by
+     * setting the {@link AccessibilityServiceInfo#FLAG_RETRIEVE_INTERACTIVE_WINDOWS}
+     * flag.
+     * </p>
+     *
+     * @return The windows of all displays if there are windows and the service is can retrieve
+     *         them, otherwise an empty list. The key of SparseArray is display ID.
+     */
+    @NonNull
+    public final SparseArray<List<AccessibilityWindowInfo>> getWindowsOnAllDisplays() {
+        return AccessibilityInteractionClient.getInstance().getWindowsOnAllDisplays(mConnectionId);
+    }
+
+    /**
+     * Gets the root node in the currently active window if this service
+     * can retrieve window content. The active window is the one that the user
+     * is currently touching or the window with input focus, if the user is not
+     * touching any window. It could be from any logical display.
+     * <p>
+     * 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);
+            }
+        }
+    }
+
+    @Override
+    public Context createDisplayContext(Display display) {
+        final Context context = super.createDisplayContext(display);
+        final int displayId = display.getDisplayId();
+        setDefaultTokenInternal(context, displayId);
+        return context;
+    }
+
+    private void setDefaultTokenInternal(Context context, int displayId) {
+        final WindowManagerImpl wm = (WindowManagerImpl) context.getSystemService(WINDOW_SERVICE);
+        final IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        IBinder token = null;
+        if (connection != null) {
+            synchronized (mLock) {
+                try {
+                    token = connection.getOverlayWindowToken(displayId);
+                } catch (RemoteException re) {
+                    Log.w(LOG_TAG, "Failed to get window token", re);
+                    re.rethrowFromSystemServer();
+                }
+            }
+            wm.setDefaultToken(token);
+        }
+    }
+
+    /**
+     * Returns the 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, 16);
+        try {
+            synchronized (mLock) {
+                mGestureStatusCallbackSequence++;
+                if (callback != null) {
+                    if (mGestureStatusCallbackInfos == null) {
+                        mGestureStatusCallbackInfos = new SparseArray<>();
+                    }
+                    GestureResultCallbackInfo callbackInfo = new GestureResultCallbackInfo(gesture,
+                            callback, handler);
+                    mGestureStatusCallbackInfos.put(mGestureStatusCallbackSequence, callbackInfo);
+                }
+                connection.dispatchGesture(mGestureStatusCallbackSequence,
+                        new ParceledListSlice<>(steps), gesture.getDisplayId());
+            }
+        } catch (RemoteException re) {
+            throw new RuntimeException(re);
+        }
+        return true;
+    }
+
+    void onPerformGestureResult(int sequence, final boolean completedSuccessfully) {
+        if (mGestureStatusCallbackInfos == null) {
+            return;
+        }
+        GestureResultCallbackInfo callbackInfo;
+        synchronized (mLock) {
+            callbackInfo = mGestureStatusCallbackInfos.get(sequence);
+            mGestureStatusCallbackInfos.remove(sequence);
+        }
+        final GestureResultCallbackInfo finalCallbackInfo = callbackInfo;
+        if ((callbackInfo != null) && (callbackInfo.gestureDescription != null)
+                && (callbackInfo.callback != null)) {
+            if (callbackInfo.handler != null) {
+                callbackInfo.handler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        if (completedSuccessfully) {
+                            finalCallbackInfo.callback
+                                    .onCompleted(finalCallbackInfo.gestureDescription);
+                        } else {
+                            finalCallbackInfo.callback
+                                    .onCancelled(finalCallbackInfo.gestureDescription);
+                        }
+                    }
+                });
+                return;
+            }
+            if (completedSuccessfully) {
+                callbackInfo.callback.onCompleted(callbackInfo.gestureDescription);
+            } else {
+                callbackInfo.callback.onCancelled(callbackInfo.gestureDescription);
+            }
+        }
+    }
+
+    private void onMagnificationChanged(int displayId, @NonNull Region region, 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);
+        }
+
+        /**
+         * Switches the current IME for the user for whom the service is enabled. The change will
+         * persist until the current IME is explicitly changed again, and may persist beyond the
+         * life cycle of the requesting service.
+         *
+         * @param imeId The ID of the input method to make current. This IME must be installed and
+         *              enabled.
+         * @return {@code true} if the current input method was successfully switched to the input
+         *         method by {@code imeId},
+         *         {@code false} if the input method specified is not installed, not enabled, or
+         *         otherwise not available to become the current IME
+         *
+         * @see android.view.inputmethod.InputMethodInfo#getId()
+         */
+        public boolean switchToInputMethod(@NonNull String imeId) {
+            final IAccessibilityServiceConnection connection =
+                    AccessibilityInteractionClient.getInstance().getConnection(
+                            mService.mConnectionId);
+            if (connection != null) {
+                try {
+                    return connection.switchToInputMethod(imeId);
+                } catch (RemoteException re) {
+                    throw new RuntimeException(re);
+                }
+            }
+            return false;
+        }
+    }
+
+    /**
+     * Returns the controller for the accessibility button within the system's navigation area.
+     * This instance may be used to query the accessibility button's state and register listeners
+     * for interactions with and state changes for the accessibility button when
+     * {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+     * <p>
+     * <strong>Note:</strong> Not all devices are capable of displaying the accessibility button
+     * within a navigation area, and as such, use of this class should be considered only as an
+     * optional feature or shortcut on supported device implementations.
+     * </p>
+     *
+     * @return the accessibility button controller for this {@link AccessibilityService}
+     */
+    @NonNull
+    public final AccessibilityButtonController getAccessibilityButtonController() {
+        return getAccessibilityButtonController(Display.DEFAULT_DISPLAY);
+    }
+
+    /**
+     * Returns the controller of specified logical display for the accessibility button within the
+     * system's navigation area. This instance may be used to query the accessibility button's
+     * state and register listeners for interactions with and state changes for the accessibility
+     * button when {@link AccessibilityServiceInfo#FLAG_REQUEST_ACCESSIBILITY_BUTTON} is set.
+     * <p>
+     * <strong>Note:</strong> Not all devices are capable of displaying the accessibility button
+     * within a navigation area, and as such, use of this class should be considered only as an
+     * optional feature or shortcut on supported device implementations.
+     * </p>
+     *
+     * @param displayId The logic display id, use {@link Display#DEFAULT_DISPLAY} for default
+     *                  display.
+     * @return the accessibility button controller for this {@link AccessibilityService}
+     */
+    @NonNull
+    public final AccessibilityButtonController getAccessibilityButtonController(int displayId) {
+        synchronized (mLock) {
+            AccessibilityButtonController controller = mAccessibilityButtonControllers.get(
+                    displayId);
+            if (controller == null) {
+                controller = new AccessibilityButtonController(
+                        AccessibilityInteractionClient.getInstance().getConnection(mConnectionId));
+                mAccessibilityButtonControllers.put(displayId, controller);
+            }
+            return controller;
+        }
+    }
+
+    private void onAccessibilityButtonClicked(int displayId) {
+        getAccessibilityButtonController(displayId).dispatchAccessibilityButtonClicked();
+    }
+
+    private void onAccessibilityButtonAvailabilityChanged(boolean available) {
+        getAccessibilityButtonController().dispatchAccessibilityButtonAvailabilityChanged(
+                available);
+    }
+
+    /** This is called when the system action list is changed. */
+    public void onSystemActionsChanged() {
+    }
+
+    /**
+     * Returns a list of system actions available in the system right now.
+     * <p>
+     * System actions that correspond to the global action constants will have matching action IDs.
+     * For example, an with id {@link #GLOBAL_ACTION_BACK} will perform the back action.
+     * </p>
+     * <p>
+     * These actions should be called by {@link #performGlobalAction}.
+     * </p>
+     *
+     * @return A list of available system actions.
+     */
+    public final @NonNull List<AccessibilityAction> getSystemActions() {
+        IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        if (connection != null) {
+            try {
+                return connection.getSystemActions();
+            } catch (RemoteException re) {
+                Log.w(LOG_TAG, "Error while calling getSystemActions", re);
+                re.rethrowFromSystemServer();
+            }
+        }
+        return Collections.emptyList();
+    }
+
+    /**
+     * Performs a global action. Such an action can be performed
+     * at any moment regardless of the current application or user
+     * location in that application. For example going back, going
+     * home, opening recents, etc.
+     *
+     * @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>
+     * <p>
+     * <strong>Note:</strong> If the view with {@link AccessibilityNodeInfo#FOCUS_INPUT}
+     * is on an embedded view hierarchy which is embedded in a {@link SurfaceView} via
+     * {@link SurfaceView#setChildSurfacePackage}, there is a limitation that this API
+     * won't be able to find the node for the view. It's because views don't know about
+     * the embedded hierarchies. Instead, you could traverse all the nodes to find the
+     * focus.
+     * </p>
+     *
+     * @param focus The focus to find. One of {@link AccessibilityNodeInfo#FOCUS_INPUT} or
+     *         {@link AccessibilityNodeInfo#FOCUS_ACCESSIBILITY}.
+     * @return The node info of the focused view or null.
+     *
+     * @see AccessibilityNodeInfo#FOCUS_INPUT
+     * @see AccessibilityNodeInfo#FOCUS_ACCESSIBILITY
+     */
+    public AccessibilityNodeInfo findFocus(int focus) {
+        return AccessibilityInteractionClient.getInstance().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);
+    }
+
+    /**
+     * Takes a screenshot of the specified display and returns it via an
+     * {@link AccessibilityService.ScreenshotResult}. You can use {@link Bitmap#wrapHardwareBuffer}
+     * to construct the bitmap from the ScreenshotResult's payload.
+     * <p>
+     * <strong>Note:</strong> In order to take screenshot your service has
+     * to declare the capability to take screenshot by setting the
+     * {@link android.R.styleable#AccessibilityService_canTakeScreenshot}
+     * property in its meta-data. For details refer to {@link #SERVICE_META_DATA}.
+     * </p>
+     *
+     * @param displayId The logic display id, must be {@link Display#DEFAULT_DISPLAY} for
+     *                  default display.
+     * @param executor Executor on which to run the callback.
+     * @param callback The callback invoked when taking screenshot has succeeded or failed.
+     *                 See {@link TakeScreenshotCallback} for details.
+     */
+    public void takeScreenshot(int displayId, @NonNull @CallbackExecutor Executor executor,
+            @NonNull TakeScreenshotCallback callback) {
+        Preconditions.checkNotNull(executor, "executor cannot be null");
+        Preconditions.checkNotNull(callback, "callback cannot be null");
+        final IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(
+                        mConnectionId);
+        if (connection == null) {
+            sendScreenshotFailure(ERROR_TAKE_SCREENSHOT_INTERNAL_ERROR, executor, callback);
+            return;
+        }
+        try {
+            connection.takeScreenshot(displayId, new RemoteCallback((result) -> {
+                final int status = result.getInt(KEY_ACCESSIBILITY_SCREENSHOT_STATUS);
+                if (status != TAKE_SCREENSHOT_SUCCESS) {
+                    sendScreenshotFailure(status, executor, callback);
+                    return;
+                }
+                final HardwareBuffer hardwareBuffer =
+                        result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_HARDWAREBUFFER);
+                final ParcelableColorSpace colorSpace =
+                        result.getParcelable(KEY_ACCESSIBILITY_SCREENSHOT_COLORSPACE);
+                final ScreenshotResult screenshot = new ScreenshotResult(hardwareBuffer,
+                        colorSpace.getColorSpace(),
+                        result.getLong(KEY_ACCESSIBILITY_SCREENSHOT_TIMESTAMP));
+                sendScreenshotSuccess(screenshot, executor, callback);
+            }));
+        } catch (RemoteException re) {
+            throw new RuntimeException(re);
+        }
+    }
+
+    /**
+     * 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(AccessibilityGestureEvent gestureEvent) {
+                return AccessibilityService.this.onGesture(gestureEvent);
+            }
+
+            @Override
+            public boolean onKeyEvent(KeyEvent event) {
+                return AccessibilityService.this.onKeyEvent(event);
+            }
+
+            @Override
+            public void onMagnificationChanged(int displayId, @NonNull Region region,
+                    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(int displayId) {
+                AccessibilityService.this.onAccessibilityButtonClicked(displayId);
+            }
+
+            @Override
+            public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+                AccessibilityService.this.onAccessibilityButtonAvailabilityChanged(available);
+            }
+
+            @Override
+            public void onSystemActionsChanged() {
+                AccessibilityService.this.onSystemActionsChanged();
+            }
+        });
+    }
+
+    /**
+     * 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 static final int DO_ON_SYSTEM_ACTIONS_CHANGED = 14;
+
+        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);
+        }
+
+        @Override
+        public void onGesture(AccessibilityGestureEvent gestureInfo) {
+            Message message = mCaller.obtainMessageO(DO_ON_GESTURE, gestureInfo);
+            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));
+        }
+
+        /** Accessibility button clicked callbacks for different displays */
+        public void onAccessibilityButtonClicked(int displayId) {
+            final Message message = mCaller.obtainMessageI(DO_ACCESSIBILITY_BUTTON_CLICKED,
+                    displayId);
+            mCaller.sendMessage(message);
+        }
+
+        public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+            final Message message = mCaller.obtainMessageI(
+                    DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED, (available ? 1 : 0));
+            mCaller.sendMessage(message);
+        }
+
+        /** This is called when the system action list is changed. */
+        public void onSystemActionsChanged() {
+            mCaller.sendMessage(mCaller.obtainMessage(DO_ON_SYSTEM_ACTIONS_CHANGED));
+        }
+
+        @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) {
+                        mCallback.onGesture((AccessibilityGestureEvent) message.obj);
+                    }
+                    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(message.arg1);
+                    }
+                    return;
+                }
+                case DO_ACCESSIBILITY_BUTTON_AVAILABILITY_CHANGED: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        final boolean available = (message.arg1 != 0);
+                        mCallback.onAccessibilityButtonAvailabilityChanged(available);
+                    }
+                    return;
+                }
+                case DO_ON_SYSTEM_ACTIONS_CHANGED: {
+                    if (mConnectionId != AccessibilityInteractionClient.NO_ID) {
+                        mCallback.onSystemActionsChanged();
+                    }
+                    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;
+        }
+    }
+
+    private void sendScreenshotSuccess(ScreenshotResult screenshot, Executor executor,
+            TakeScreenshotCallback callback) {
+        executor.execute(() -> callback.onSuccess(screenshot));
+    }
+
+    private void sendScreenshotFailure(@ScreenshotErrorCode int errorCode, Executor executor,
+            TakeScreenshotCallback callback) {
+        executor.execute(() -> callback.onFailure(errorCode));
+    }
+
+    /**
+     * Interface used to report status of taking screenshot.
+     */
+    public interface TakeScreenshotCallback {
+        /** Called when taking screenshot has completed successfully.
+         *
+         * @param screenshot The content of screenshot.
+         */
+        void onSuccess(@NonNull ScreenshotResult screenshot);
+
+        /** Called when taking screenshot has failed. {@code errorCode} will identify the
+         * reason of failure.
+         *
+         * @param errorCode The error code of this operation.
+         */
+        void onFailure(@ScreenshotErrorCode int errorCode);
+    }
+
+    /**
+     * Can be used to construct a bitmap of the screenshot or any other operations for
+     * {@link AccessibilityService#takeScreenshot} API.
+     */
+    public static final class ScreenshotResult {
+        private final @NonNull HardwareBuffer mHardwareBuffer;
+        private final @NonNull ColorSpace mColorSpace;
+        private final long mTimestamp;
+
+        private ScreenshotResult(@NonNull HardwareBuffer hardwareBuffer,
+                @NonNull ColorSpace colorSpace, long timestamp) {
+            Preconditions.checkNotNull(hardwareBuffer, "hardwareBuffer cannot be null");
+            Preconditions.checkNotNull(colorSpace, "colorSpace cannot be null");
+            mHardwareBuffer = hardwareBuffer;
+            mColorSpace = colorSpace;
+            mTimestamp = timestamp;
+        }
+
+        /**
+         * Gets the {@link ColorSpace} identifying a specific organization of colors of the
+         * screenshot.
+         *
+         * @return the color space
+         */
+        @NonNull
+        public ColorSpace getColorSpace() {
+            return mColorSpace;
+        }
+
+        /**
+         * Gets the {@link HardwareBuffer} representing a memory buffer of the screenshot.
+         * <p>
+         * <strong>Note:</strong> The application should call {@link HardwareBuffer#close()} when
+         * the buffer is no longer needed to free the underlying resources.
+         * </p>
+         *
+         * @return the hardware buffer
+         */
+        @NonNull
+        public HardwareBuffer getHardwareBuffer() {
+            return mHardwareBuffer;
+        }
+
+        /**
+         * Gets the timestamp of taking the screenshot.
+         *
+         * @return milliseconds of non-sleep uptime before screenshot since boot and it's from
+         * {@link SystemClock#uptimeMillis()}
+         */
+        public long getTimestamp() {
+            return mTimestamp;
+        };
+    }
+
+    /**
+     * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, this
+     * function requests that touch interactions starting in the specified region of the screen
+     * bypass the gesture detector. There can only be one gesture detection passthrough region per
+     * display. Requesting a new gesture detection passthrough region clears the existing one. To
+     * disable this passthrough and return to the original behavior, pass in an empty region. When
+     * {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this
+     * function has no effect.
+     *
+     * @param displayId The display on which to set this region.
+     * @param region the region of the screen.
+     */
+    public void setGestureDetectionPassthroughRegion(int displayId, @NonNull Region region) {
+        Preconditions.checkNotNull(region, "region cannot be null");
+        final IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        if (connection != null) {
+            try {
+                connection.setGestureDetectionPassthroughRegion(displayId, region);
+            } catch (RemoteException re) {
+                throw new RuntimeException(re);
+            }
+        }
+    }
+
+    /**
+     * When {@link AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled, this
+     * function requests that touch interactions starting in the specified region of the screen
+     * bypass the touch explorer and go straight to the view hierarchy. There can only be one touch
+     * exploration passthrough region per display. Requesting a new touch explorationpassthrough
+     * region clears the existing one. To disable this passthrough and return to the original
+     * behavior, pass in an empty region. When {@link
+     * AccessibilityServiceInfo#FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this function has
+     * no effect.
+     *
+     * @param displayId The display on which to set this region.
+     * @param region the region of the screen .
+     */
+    public void setTouchExplorationPassthroughRegion(int displayId, @NonNull Region region) {
+        Preconditions.checkNotNull(region, "region cannot be null");
+        final IAccessibilityServiceConnection connection =
+                AccessibilityInteractionClient.getInstance().getConnection(mConnectionId);
+        if (connection != null) {
+            try {
+                connection.setTouchExplorationPassthroughRegion(displayId, region);
+            } catch (RemoteException re) {
+                throw new RuntimeException(re);
+            }
+        }
+    }
+}
diff --git a/android/accessibilityservice/AccessibilityServiceInfo.java b/android/accessibilityservice/AccessibilityServiceInfo.java
new file mode 100644
index 0000000..ca22bf4
--- /dev/null
+++ b/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -0,0 +1,1420 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice;
+
+import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHtmlText;
+import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage;
+import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.compat.annotation.ChangeId;
+import android.compat.annotation.EnabledAfter;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.hardware.fingerprint.FingerprintManager;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.AttributeSet;
+import android.util.SparseArray;
+import android.util.TypedValue;
+import android.util.Xml;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+
+import com.android.internal.R;
+import com.android.internal.compat.IPlatformCompat;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * This class describes an {@link AccessibilityService}. The system notifies an
+ * {@link AccessibilityService} for {@link android.view.accessibility.AccessibilityEvent}s
+ * according to the information encapsulated in this class.
+ *
+ * <div class="special reference">
+ * <h3>Developer Guides</h3>
+ * <p>For more information about creating AccessibilityServices, read the
+ * <a href="{@docRoot}guide/topics/ui/accessibility/index.html">Accessibility</a>
+ * developer guide.</p>
+ * </div>
+ *
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityEventTypes
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityFeedbackType
+ * @attr ref android.R.styleable#AccessibilityService_accessibilityFlags
+ * @attr ref android.R.styleable#AccessibilityService_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
+ * @attr ref android.R.styleable#AccessibilityService_canTakeScreenshot
+ * @see AccessibilityService
+ * @see android.view.accessibility.AccessibilityEvent
+ * @see android.view.accessibility.AccessibilityManager
+ */
+public class AccessibilityServiceInfo implements Parcelable {
+
+    private static final String TAG_ACCESSIBILITY_SERVICE = "accessibility-service";
+
+    /**
+     * Capability: This accessibility service can retrieve the active window content.
+     * @see android.R.styleable#AccessibilityService_canRetrieveWindowContent
+     */
+    public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 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;
+
+    /**
+     * Capability: This accessibility service can take screenshot.
+     * @see android.R.styleable#AccessibilityService_canTakeScreenshot
+     */
+    public static final int CAPABILITY_CAN_TAKE_SCREENSHOT = 0x00000080;
+
+    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.
+      * <p>
+      *   <strong>Note:</strong> For accessibility services targeting APIs greater than
+      *   {@link Build.VERSION_CODES#Q API 29}, this flag must be specified in the
+      *   accessibility service metadata file. Otherwise, it will be ignored.
+      * </p>
+     */
+    public static final int FLAG_REQUEST_ACCESSIBILITY_BUTTON = 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;
+
+    /**
+     * This flag requests that when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled,
+     * double tap and double tap and hold gestures are dispatched to the service rather than being
+     * handled by the framework. If {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this
+     * flag has no effect.
+     *
+     * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+     */
+    public static final int FLAG_SERVICE_HANDLES_DOUBLE_TAP = 0x0000800;
+
+    /**
+     * This flag requests that when when {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is enabled,
+     * multi-finger gestures are also enabled. As a consequence, two-finger bypass gestures will be
+     * disabled. If {@link #FLAG_REQUEST_TOUCH_EXPLORATION_MODE} is disabled this flag has no
+     * effect.
+     *
+     * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+     */
+    public static final int FLAG_REQUEST_MULTI_FINGER_GESTURES = 0x0001000;
+
+    /** {@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>
+     * <p>
+     *   <strong>Note:</strong> Accessibility services with targetSdkVersion greater than
+     *   {@link Build.VERSION_CODES#Q API 29} cannot dynamically set the
+     *   {@link #FLAG_REQUEST_ACCESSIBILITY_BUTTON} at runtime. It must be specified in the
+     *   accessibility service metadata file.
+     * </p>
+     * @see #DEFAULT
+     * @see #FLAG_INCLUDE_NOT_IMPORTANT_VIEWS
+     * @see #FLAG_REQUEST_TOUCH_EXPLORATION_MODE
+     * @see #FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY
+     * @see #FLAG_REQUEST_FILTER_KEY_EVENTS
+     * @see #FLAG_REPORT_VIEW_IDS
+     * @see #FLAG_RETRIEVE_INTERACTIVE_WINDOWS
+     * @see #FLAG_ENABLE_ACCESSIBILITY_VOLUME
+     * @see #FLAG_REQUEST_ACCESSIBILITY_BUTTON
+     * @see #FLAG_REQUEST_SHORTCUT_WARNING_DIALOG_SPOKEN_FEEDBACK
+     */
+    public int flags;
+
+    /**
+     * Whether or not the service has crashed and is awaiting restart. Only valid from {@link
+     * android.view.accessibility.AccessibilityManager#getInstalledAccessibilityServiceList()},
+     * because that is populated from the internal list of running services.
+     *
+     * @hide
+     */
+    public boolean crashed;
+
+    /**
+     * A recommended timeout in milliseconds for non-interactive controls.
+     */
+    private int mNonInteractiveUiTimeout;
+
+    /**
+     * A recommended timeout in milliseconds for interactive controls.
+     */
+    private int mInteractiveUiTimeout;
+
+    /**
+     * The component name the accessibility service.
+     */
+    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;
+
+    /**
+     * For accessibility services targeting APIs greater than {@link Build.VERSION_CODES#Q API 29},
+     * {@link #FLAG_REQUEST_ACCESSIBILITY_BUTTON} must be specified in the accessibility service
+     * metadata file. Otherwise, it will be ignored.
+     */
+    @ChangeId
+    @EnabledAfter(targetSdkVersion = android.os.Build.VERSION_CODES.Q)
+    private static final long REQUEST_ACCESSIBILITY_BUTTON_CHANGE = 136293963L;
+
+    /**
+     * Resource id of the animated image of the accessibility service.
+     */
+    private int mAnimatedImageRes;
+
+    /**
+     * Resource id of the html description of the accessibility service.
+     */
+    private int mHtmlDescriptionRes;
+
+    /**
+     * 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;
+            }
+            if (asAttributes.getBoolean(com.android.internal.R.styleable
+                    .AccessibilityService_canTakeScreenshot, false)) {
+                mCapabilities |= CAPABILITY_CAN_TAKE_SCREENSHOT;
+            }
+            TypedValue peekedValue = asAttributes.peekValue(
+                    com.android.internal.R.styleable.AccessibilityService_description);
+            if (peekedValue != null) {
+                mDescriptionResId = peekedValue.resourceId;
+                CharSequence nonLocalizedDescription = peekedValue.coerceToString();
+                if (nonLocalizedDescription != null) {
+                    mNonLocalizedDescription = nonLocalizedDescription.toString().trim();
+                }
+            }
+            peekedValue = asAttributes.peekValue(
+                com.android.internal.R.styleable.AccessibilityService_summary);
+            if (peekedValue != null) {
+                mSummaryResId = peekedValue.resourceId;
+                CharSequence nonLocalizedSummary = peekedValue.coerceToString();
+                if (nonLocalizedSummary != null) {
+                    mNonLocalizedSummary = nonLocalizedSummary.toString().trim();
+                }
+            }
+            peekedValue = asAttributes.peekValue(
+                    com.android.internal.R.styleable.AccessibilityService_animatedImageDrawable);
+            if (peekedValue != null) {
+                mAnimatedImageRes = peekedValue.resourceId;
+            }
+            peekedValue = asAttributes.peekValue(
+                    com.android.internal.R.styleable.AccessibilityService_htmlDescription);
+            if (peekedValue != null) {
+                mHtmlDescriptionRes = peekedValue.resourceId;
+            }
+            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 AccessibilityService can change dynamically.
+     * <p>
+     * Note: A11y services targeting APIs > Q, it cannot update flagRequestAccessibilityButton
+     * dynamically.
+     * </p>
+     *
+     * @param platformCompat The platform compat service to check the compatibility change.
+     * @param other The info from which to update the properties.
+     *
+     * @hide
+     */
+    public void updateDynamicallyConfigurableProperties(IPlatformCompat platformCompat,
+            AccessibilityServiceInfo other) {
+        if (isRequestAccessibilityButtonChangeEnabled(platformCompat)) {
+            other.flags &= ~FLAG_REQUEST_ACCESSIBILITY_BUTTON;
+            other.flags |= (flags & FLAG_REQUEST_ACCESSIBILITY_BUTTON);
+        }
+        eventTypes = other.eventTypes;
+        packageNames = other.packageNames;
+        feedbackType = other.feedbackType;
+        notificationTimeout = other.notificationTimeout;
+        mNonInteractiveUiTimeout = other.mNonInteractiveUiTimeout;
+        mInteractiveUiTimeout = other.mInteractiveUiTimeout;
+        flags = other.flags;
+    }
+
+    private boolean isRequestAccessibilityButtonChangeEnabled(IPlatformCompat platformCompat) {
+        if (mResolveInfo == null) {
+            return true;
+        }
+        try {
+            if (platformCompat != null) {
+                return platformCompat.isChangeEnabled(REQUEST_ACCESSIBILITY_BUTTON_CHANGE,
+                        mResolveInfo.serviceInfo.applicationInfo);
+            }
+        } catch (RemoteException ignore) {
+        }
+        return mResolveInfo.serviceInfo.applicationInfo.targetSdkVersion > Build.VERSION_CODES.Q;
+    }
+
+    /**
+     * @hide
+     */
+    public void setComponentName(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;
+    }
+
+    /**
+     * Gets the animated image resource id.
+     *
+     * @return The animated image resource id.
+     *
+     * @hide
+     */
+    public int getAnimatedImageRes() {
+        return mAnimatedImageRes;
+    }
+
+    /**
+     * The animated image drawable.
+     * <p>
+     *    Image can not exceed the screen size.
+     *    <strong>Statically set from
+     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+     * </p>
+     * @return The animated image drawable, or null if the resource is invalid or the image
+     * exceed the screen size.
+     *
+     * @hide
+     */
+    @Nullable
+    public Drawable loadAnimatedImage(@NonNull Context context)  {
+        if (mAnimatedImageRes == /* invalid */ 0) {
+            return null;
+        }
+
+        return loadSafeAnimatedImage(context, mResolveInfo.serviceInfo.applicationInfo,
+                mAnimatedImageRes);
+    }
+
+    /**
+     * Whether this service can retrieve the current window's content.
+     * <p>
+     *    <strong>Statically set from
+     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+     * </p>
+     * @return True if window content can be retrieved.
+     *
+     * @deprecated Use {@link #getCapabilities()}.
+     */
+    public boolean getCanRetrieveWindowContent() {
+        return (mCapabilities & CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT) != 0;
+    }
+
+    /**
+     * Returns the bit mask of capabilities this accessibility service has such as
+     * being able to retrieve the active window content, etc.
+     *
+     * @return The capability bit mask.
+     *
+     * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+     * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+     * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+     * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
+     * @see #CAPABILITY_CAN_PERFORM_GESTURES
+     * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
+     */
+    public int getCapabilities() {
+        return mCapabilities;
+    }
+
+    /**
+     * Sets the bit mask of capabilities this accessibility service has such as
+     * being able to retrieve the active window content, etc.
+     *
+     * @param capabilities The capability bit mask.
+     *
+     * @see #CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT
+     * @see #CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION
+     * @see #CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS
+     * @see #CAPABILITY_CAN_CONTROL_MAGNIFICATION
+     * @see #CAPABILITY_CAN_PERFORM_GESTURES
+     * @see #CAPABILITY_CAN_TAKE_SCREENSHOT
+     *
+     * @hide
+     */
+    @UnsupportedAppUsage
+    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;
+    }
+
+    /**
+     * The localized and restricted html description of the accessibility service.
+     * <p>
+     *    Filters the <img> tag which do not meet the custom specification and the <a> tag.
+     *    <strong>Statically set from
+     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
+     * </p>
+     * @return The localized and restricted html description.
+     *
+     * @hide
+     */
+    @Nullable
+    public String loadHtmlDescription(@NonNull PackageManager packageManager) {
+        if (mHtmlDescriptionRes == /* invalid */ 0) {
+            return null;
+        }
+
+        final ServiceInfo serviceInfo = mResolveInfo.serviceInfo;
+        final CharSequence htmlDescription = packageManager.getText(serviceInfo.packageName,
+                mHtmlDescriptionRes, serviceInfo.applicationInfo);
+        if (htmlDescription != null) {
+            return getFilteredHtmlText(htmlDescription.toString().trim());
+        }
+        return null;
+    }
+
+    /**
+     * Set the recommended time that non-interactive controls need to remain on the screen to
+     * support the user.
+     * <p>
+     *     <strong>This value can be dynamically set at runtime by
+     *     {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}.</strong>
+     * </p>
+     *
+     * @param timeout The timeout in milliseconds.
+     *
+     * @see android.R.styleable#AccessibilityService_nonInteractiveUiTimeout
+     */
+    public void setNonInteractiveUiTimeoutMillis(@IntRange(from = 0) int timeout) {
+        mNonInteractiveUiTimeout = timeout;
+    }
+
+    /**
+     * Get the recommended timeout for non-interactive controls.
+     *
+     * @return The timeout in milliseconds.
+     *
+     * @see #setNonInteractiveUiTimeoutMillis(int)
+     */
+    public int getNonInteractiveUiTimeoutMillis() {
+        return mNonInteractiveUiTimeout;
+    }
+
+    /**
+     * Set the recommended time that interactive controls need to remain on the screen to
+     * support the user.
+     * <p>
+     *     <strong>This value can be dynamically set at runtime by
+     *     {@link AccessibilityService#setServiceInfo(AccessibilityServiceInfo)}.</strong>
+     * </p>
+     *
+     * @param timeout The timeout in milliseconds.
+     *
+     * @see android.R.styleable#AccessibilityService_interactiveUiTimeout
+     */
+    public void setInteractiveUiTimeoutMillis(@IntRange(from = 0) int timeout) {
+        mInteractiveUiTimeout = timeout;
+    }
+
+    /**
+     * Get the recommended timeout for interactive controls.
+     *
+     * @return The timeout in milliseconds.
+     *
+     * @see #setInteractiveUiTimeoutMillis(int)
+     */
+    public int getInteractiveUiTimeoutMillis() {
+        return mInteractiveUiTimeout;
+    }
+
+    /** {@hide} */
+    public boolean isDirectBootAware() {
+        return ((flags & FLAG_FORCE_DIRECT_BOOT_AWARE) != 0)
+                || mResolveInfo.serviceInfo.directBootAware;
+    }
+
+    /**
+     * {@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.writeInt(mAnimatedImageRes);
+        parcel.writeInt(mHtmlDescriptionRes);
+        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();
+        mAnimatedImageRes = parcel.readInt();
+        mHtmlDescriptionRes = 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_SERVICE_HANDLES_DOUBLE_TAP:
+                return "FLAG_SERVICE_HANDLES_DOUBLE_TAP";
+            case FLAG_REQUEST_MULTI_FINGER_GESTURES:
+                return "FLAG_REQUEST_MULTI_FINGER_GESTURES";
+            case FLAG_REQUEST_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";
+            case CAPABILITY_CAN_TAKE_SCREENSHOT:
+                return "CAPABILITY_CAN_TAKE_SCREENSHOT";
+            default:
+                return "UNKNOWN";
+        }
+    }
+
+    /**
+     * @hide
+     * @return The list of {@link CapabilityInfo} objects.
+     * @deprecated The version that takes a context works better.
+     */
+    public List<CapabilityInfo> getCapabilityInfos() {
+        return getCapabilityInfos(null);
+    }
+
+    /**
+     * @hide
+     * @param context A valid context
+     * @return The list of {@link CapabilityInfo} objects.
+     */
+    public List<CapabilityInfo> getCapabilityInfos(Context context) {
+        if (mCapabilities == 0) {
+            return Collections.emptyList();
+        }
+        int capabilities = mCapabilities;
+        List<CapabilityInfo> capabilityInfos = new ArrayList<CapabilityInfo>();
+        SparseArray<CapabilityInfo> capabilityInfoSparseArray =
+                getCapabilityInfoSparseArray(context);
+        while (capabilities != 0) {
+            final int capabilityBit = 1 << Integer.numberOfTrailingZeros(capabilities);
+            capabilities &= ~capabilityBit;
+            CapabilityInfo capabilityInfo = capabilityInfoSparseArray.get(capabilityBit);
+            if (capabilityInfo != null) {
+                capabilityInfos.add(capabilityInfo);
+            }
+        }
+        return capabilityInfos;
+    }
+
+    private static SparseArray<CapabilityInfo> getCapabilityInfoSparseArray(Context context) {
+        if (sAvailableCapabilityInfos == null) {
+            sAvailableCapabilityInfos = new SparseArray<CapabilityInfo>();
+            sAvailableCapabilityInfos.put(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+                    new CapabilityInfo(CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT,
+                            R.string.capability_title_canRetrieveWindowContent,
+                            R.string.capability_desc_canRetrieveWindowContent));
+            sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+                    new CapabilityInfo(CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION,
+                            R.string.capability_title_canRequestTouchExploration,
+                            R.string.capability_desc_canRequestTouchExploration));
+            sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+                    new CapabilityInfo(CAPABILITY_CAN_REQUEST_FILTER_KEY_EVENTS,
+                            R.string.capability_title_canRequestFilterKeyEvents,
+                            R.string.capability_desc_canRequestFilterKeyEvents));
+            sAvailableCapabilityInfos.put(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+                    new CapabilityInfo(CAPABILITY_CAN_CONTROL_MAGNIFICATION,
+                            R.string.capability_title_canControlMagnification,
+                            R.string.capability_desc_canControlMagnification));
+            sAvailableCapabilityInfos.put(CAPABILITY_CAN_PERFORM_GESTURES,
+                    new CapabilityInfo(CAPABILITY_CAN_PERFORM_GESTURES,
+                            R.string.capability_title_canPerformGestures,
+                            R.string.capability_desc_canPerformGestures));
+            sAvailableCapabilityInfos.put(CAPABILITY_CAN_TAKE_SCREENSHOT,
+                    new CapabilityInfo(CAPABILITY_CAN_TAKE_SCREENSHOT,
+                            R.string.capability_title_canTakeScreenshot,
+                            R.string.capability_desc_canTakeScreenshot));
+            if ((context == null) || fingerprintAvailable(context)) {
+                sAvailableCapabilityInfos.put(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
+                        new CapabilityInfo(CAPABILITY_CAN_REQUEST_FINGERPRINT_GESTURES,
+                                R.string.capability_title_canCaptureFingerprintGestures,
+                                R.string.capability_desc_canCaptureFingerprintGestures));
+            }
+        }
+        return sAvailableCapabilityInfos;
+    }
+
+    private static boolean fingerprintAvailable(Context context) {
+        return context.getPackageManager().hasSystemFeature(FEATURE_FINGERPRINT)
+                && context.getSystemService(FingerprintManager.class).isHardwareDetected();
+    }
+    /**
+     * @hide
+     */
+    public static final class CapabilityInfo {
+        public final int capability;
+        public final int titleResId;
+        public final int descResId;
+
+        public CapabilityInfo(int capability, int titleResId, int descResId) {
+            this.capability = capability;
+            this.titleResId = titleResId;
+            this.descResId = descResId;
+        }
+    }
+
+    /**
+     * @see Parcelable.Creator
+     */
+    public static final @android.annotation.NonNull Parcelable.Creator<AccessibilityServiceInfo> CREATOR =
+            new Parcelable.Creator<AccessibilityServiceInfo>() {
+        public AccessibilityServiceInfo createFromParcel(Parcel parcel) {
+            AccessibilityServiceInfo info = new AccessibilityServiceInfo();
+            info.initFromParcel(parcel);
+            return info;
+        }
+
+        public AccessibilityServiceInfo[] newArray(int size) {
+            return new AccessibilityServiceInfo[size];
+        }
+    };
+}
diff --git a/android/accessibilityservice/AccessibilityShortcutInfo.java b/android/accessibilityservice/AccessibilityShortcutInfo.java
new file mode 100644
index 0000000..623734e
--- /dev/null
+++ b/android/accessibilityservice/AccessibilityShortcutInfo.java
@@ -0,0 +1,322 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice;
+
+import static android.accessibilityservice.util.AccessibilityUtils.getFilteredHtmlText;
+import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Xml;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
+
+/**
+ * Activities of interest to users with accessibility needs may request to be targets of the
+ * accessibility shortcut. These activities must handle the
+ * {@link Intent#ACTION_MAIN} intent with category
+ * {@link Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET}, which will be dispatched by the system
+ * when the user activates the shortcut when it is configured to point at this target.
+ *
+ * @see Intent#CATEGORY_ACCESSIBILITY_SHORTCUT_TARGET
+ *
+ * @hide
+ */
+public final class AccessibilityShortcutInfo {
+    private static final String TAG_ACCESSIBILITY_SHORTCUT = "accessibility-shortcut-target";
+
+    /**
+     * Name under which an activity component of the accessibility shortcut publishes information
+     * about itself. This meta-data must reference an XML resource containing an
+     * <code>&lt;accessibility-shortcut-target&gt;</code> tag.
+     */
+    public static final String META_DATA = "android.accessibilityshortcut.target";
+
+    /**
+     * The component name of the accessibility shortcut target.
+     */
+    private final ComponentName mComponentName;
+
+    /**
+     * The activity info of the accessibility shortcut target.
+     */
+    private final ActivityInfo mActivityInfo;
+
+    /**
+     * Resource id of the summary of the accessibility shortcut target.
+     */
+    private final int mSummaryResId;
+
+    /**
+     * Resource id of the description of the accessibility shortcut target.
+     */
+    private final int mDescriptionResId;
+
+    /**
+     * Resource id of the animated image of the accessibility shortcut target.
+     */
+    private final int mAnimatedImageRes;
+
+    /**
+     * Resource id of the html description of the accessibility shortcut target.
+     */
+    private final int mHtmlDescriptionRes;
+
+    /**
+     * The accessibility shortcut target setting activity's name, used by the system
+     * settings to launch the setting activity of this accessibility shortcut target.
+     */
+    private String mSettingsActivityName;
+
+    /**
+     * Creates a new instance.
+     *
+     * @param context Context for accessing resources.
+     * @param activityInfo The activity info.
+     * @throws XmlPullParserException If a XML parsing error occurs.
+     * @throws IOException If a XML parsing error occurs.
+     */
+    public AccessibilityShortcutInfo(@NonNull Context context, @NonNull ActivityInfo activityInfo)
+            throws XmlPullParserException, IOException {
+        final PackageManager packageManager = context.getPackageManager();
+        mComponentName = activityInfo.getComponentName();
+        mActivityInfo = activityInfo;
+
+        try (XmlResourceParser parser = mActivityInfo.loadXmlMetaData(
+                packageManager, META_DATA)) {
+            if (parser == null) {
+                throw new XmlPullParserException("Meta-data "
+                        + TAG_ACCESSIBILITY_SHORTCUT + " does not exist");
+            }
+
+            int type = 0;
+            while (type != XmlPullParser.END_DOCUMENT && type != XmlPullParser.START_TAG) {
+                type = parser.next();
+            }
+
+            final String nodeName = parser.getName();
+            if (!TAG_ACCESSIBILITY_SHORTCUT.equals(nodeName)) {
+                throw new XmlPullParserException("Meta-data does not start with"
+                        + TAG_ACCESSIBILITY_SHORTCUT + " tag");
+            }
+
+            final AttributeSet allAttributes = Xml.asAttributeSet(parser);
+            final Resources resources = packageManager.getResourcesForApplication(
+                    mActivityInfo.applicationInfo);
+            final TypedArray asAttributes = resources.obtainAttributes(allAttributes,
+                    com.android.internal.R.styleable.AccessibilityShortcutTarget);
+
+            // Gets description
+            mDescriptionResId = asAttributes.getResourceId(
+                    com.android.internal.R.styleable.AccessibilityShortcutTarget_description, 0);
+            // Gets summary
+            mSummaryResId = asAttributes.getResourceId(
+                    com.android.internal.R.styleable.AccessibilityShortcutTarget_summary, 0);
+            // Gets animated image
+            mAnimatedImageRes = asAttributes.getResourceId(
+                    com.android.internal.R.styleable
+                            .AccessibilityShortcutTarget_animatedImageDrawable, /* defValue= */ 0);
+            // Gets html description
+            mHtmlDescriptionRes = asAttributes.getResourceId(
+                    com.android.internal.R.styleable.AccessibilityShortcutTarget_htmlDescription,
+                    0);
+            // Get settings activity name
+            mSettingsActivityName = asAttributes.getString(
+                    com.android.internal.R.styleable.AccessibilityShortcutTarget_settingsActivity);
+            asAttributes.recycle();
+
+            if ((mDescriptionResId == 0 && mHtmlDescriptionRes == 0) || mSummaryResId == 0) {
+                throw new XmlPullParserException("No description or summary in meta-data");
+            }
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new XmlPullParserException("Unable to create context for: "
+                    + mActivityInfo.packageName);
+        }
+    }
+
+    /**
+     * The {@link ActivityInfo} of accessibility shortcut target.
+     *
+     * @return The activity info.
+     */
+    @NonNull
+    public ActivityInfo getActivityInfo() {
+        return mActivityInfo;
+    }
+
+    /**
+     * The {@link ComponentName} of the accessibility shortcut target.
+     *
+     * @return The component name
+     */
+    @NonNull
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /**
+     * The localized summary of the accessibility shortcut target.
+     *
+     * @return The localized summary if available, and {@code null} if a summary
+     * has not been provided.
+     */
+    @Nullable
+    public String loadSummary(@NonNull PackageManager packageManager) {
+        return loadResourceString(packageManager, mActivityInfo, mSummaryResId);
+    }
+
+    /**
+     * The localized description of the accessibility shortcut target.
+     *
+     * @return The localized description.
+     */
+    @Nullable
+    public String loadDescription(@NonNull PackageManager packageManager) {
+        return loadResourceString(packageManager, mActivityInfo, mDescriptionResId);
+    }
+
+    /**
+     * Gets the animated image resource id.
+     *
+     * @return The animated image resource id.
+     *
+     * @hide
+     */
+    public int getAnimatedImageRes() {
+        return mAnimatedImageRes;
+    }
+
+    /**
+     * The animated image drawable of the accessibility shortcut target.
+     *
+     * @return The animated image drawable, or null if the resource is invalid or the image
+     * exceed the screen size.
+     *
+     * @hide
+     */
+    @Nullable
+    public Drawable loadAnimatedImage(@NonNull Context context) {
+        if (mAnimatedImageRes == /* invalid */ 0) {
+            return null;
+        }
+
+        return loadSafeAnimatedImage(context, mActivityInfo.applicationInfo, mAnimatedImageRes);
+    }
+
+    /**
+     * The localized and restricted html description of the accessibility shortcut target.
+     * It filters the <img> tag which do not meet the custom specification and the <a> tag.
+     *
+     * @return The localized and restricted html description.
+     *
+     * @hide
+     */
+    @Nullable
+    public String loadHtmlDescription(@NonNull PackageManager packageManager) {
+        final String htmlDescription = loadResourceString(packageManager, mActivityInfo,
+                mHtmlDescriptionRes);
+        if (htmlDescription != null) {
+            return getFilteredHtmlText(htmlDescription);
+        }
+        return null;
+    }
+
+    /**
+     * The settings activity name.
+     *
+     * @return The settings activity name.
+     */
+    @Nullable
+    public String getSettingsActivityName() {
+        return mSettingsActivityName;
+    }
+
+    /**
+     * Gets string resource by the given activity and resource id.
+     */
+    @Nullable
+    private String loadResourceString(@NonNull PackageManager packageManager,
+            @NonNull ActivityInfo activityInfo, int resId) {
+        if (resId == 0) {
+            return null;
+        }
+        final CharSequence text = packageManager.getText(activityInfo.packageName,
+                resId, activityInfo.applicationInfo);
+        if (text != null) {
+            return text.toString().trim();
+        }
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+        return 31 * 1 + ((mComponentName == null) ? 0 : mComponentName.hashCode());
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (getClass() != obj.getClass()) {
+            return false;
+        }
+        final AccessibilityShortcutInfo other = (AccessibilityShortcutInfo) obj;
+        if (mComponentName == null) {
+            if (other.mComponentName != null) {
+                return false;
+            }
+        } else if (!mComponentName.equals(other.mComponentName)) {
+            return false;
+        }
+        return true;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public String toString() {
+        StringBuilder stringBuilder = new StringBuilder();
+        stringBuilder.append("AccessibilityShortcutInfo[");
+        stringBuilder.append("activityInfo: ").append(mActivityInfo);
+        stringBuilder.append("]");
+        return stringBuilder.toString();
+    }
+}
diff --git a/android/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..a821dad
--- /dev/null
+++ b/android/accessibilityservice/GestureDescription.java
@@ -0,0 +1,603 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice;
+
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.graphics.Path;
+import android.graphics.PathMeasure;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.view.Display;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Accessibility services with the
+ * {@link android.R.styleable#AccessibilityService_canPerformGestures} property can dispatch
+ * gestures. This class describes those gestures. Gestures are made up of one or more strokes.
+ * Gestures are immutable once built and will be dispatched to the specified display.
+ * <p>
+ * Spatial dimensions throughout are in screen pixels. Time is measured in milliseconds.
+ */
+public final class GestureDescription {
+    /** Gestures may contain no more than this many strokes */
+    private static final int MAX_STROKE_COUNT = 20;
+
+    /**
+     * Upper bound on total gesture duration. Nearly all gestures will be much shorter.
+     */
+    private static final long MAX_GESTURE_DURATION_MS = 60 * 1000;
+
+    private final List<StrokeDescription> mStrokes = new ArrayList<>();
+    private final float[] mTempPos = new float[2];
+    private final int mDisplayId;
+
+    /**
+     * Get the upper limit for the number of strokes a gesture may contain.
+     *
+     * @return The maximum number of strokes.
+     */
+    public static int getMaxStrokeCount() {
+        return MAX_STROKE_COUNT;
+    }
+
+    /**
+     * Get the upper limit on a gesture's duration.
+     *
+     * @return The maximum duration in milliseconds.
+     */
+    public static long getMaxGestureDuration() {
+        return MAX_GESTURE_DURATION_MS;
+    }
+
+    private GestureDescription() {
+       this(new ArrayList<>());
+    }
+
+    private GestureDescription(List<StrokeDescription> strokes) {
+        this(strokes, Display.DEFAULT_DISPLAY);
+    }
+
+    private GestureDescription(List<StrokeDescription> strokes, int displayId) {
+        mStrokes.addAll(strokes);
+        mDisplayId = displayId;
+    }
+
+    /**
+     * Get the number of stroke in the gesture.
+     *
+     * @return the number of strokes in this gesture
+     */
+    public int getStrokeCount() {
+        return mStrokes.size();
+    }
+
+    /**
+     * Read a stroke from the gesture
+     *
+     * @param index the index of the stroke
+     *
+     * @return A description of the stroke.
+     */
+    public StrokeDescription getStroke(@IntRange(from = 0) int index) {
+        return mStrokes.get(index);
+    }
+
+    /**
+     * Returns the ID of the display this gesture is sent on, for use with
+     * {@link android.hardware.display.DisplayManager#getDisplay(int)}.
+     *
+     * @return The logical display id.
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /**
+     * Return the smallest key point (where a path starts or ends) that is at least a specified
+     * offset
+     * @param offset the minimum start time
+     * @return The next key time that is at least the offset or -1 if one can't be found
+     */
+    private long getNextKeyPointAtLeast(long offset) {
+        long nextKeyPoint = Long.MAX_VALUE;
+        for (int i = 0; i < mStrokes.size(); i++) {
+            long thisStartTime = mStrokes.get(i).mStartTime;
+            if ((thisStartTime < nextKeyPoint) && (thisStartTime >= offset)) {
+                nextKeyPoint = thisStartTime;
+            }
+            long thisEndTime = mStrokes.get(i).mEndTime;
+            if ((thisEndTime < nextKeyPoint) && (thisEndTime >= offset)) {
+                nextKeyPoint = thisEndTime;
+            }
+        }
+        return (nextKeyPoint == Long.MAX_VALUE) ? -1L : nextKeyPoint;
+    }
+
+    /**
+     * Get the points that correspond to a particular moment in time.
+     * @param time The time of interest
+     * @param touchPoints An array to hold the current touch points. Must be preallocated to at
+     * least the number of paths in the gesture to prevent going out of bounds
+     * @return The number of points found, and thus the number of elements set in each array
+     */
+    private int getPointsForTime(long time, TouchPoint[] touchPoints) {
+        int numPointsFound = 0;
+        for (int i = 0; i < mStrokes.size(); i++) {
+            StrokeDescription strokeDescription = mStrokes.get(i);
+            if (strokeDescription.hasPointForTime(time)) {
+                touchPoints[numPointsFound].mStrokeId = strokeDescription.getId();
+                touchPoints[numPointsFound].mContinuedStrokeId =
+                        strokeDescription.getContinuedStrokeId();
+                touchPoints[numPointsFound].mIsStartOfPath =
+                        (strokeDescription.getContinuedStrokeId() < 0)
+                                && (time == strokeDescription.mStartTime);
+                touchPoints[numPointsFound].mIsEndOfPath = !strokeDescription.willContinue()
+                        && (time == strokeDescription.mEndTime);
+                strokeDescription.getPosForTime(time, mTempPos);
+                touchPoints[numPointsFound].mX = Math.round(mTempPos[0]);
+                touchPoints[numPointsFound].mY = Math.round(mTempPos[1]);
+                numPointsFound++;
+            }
+        }
+        return numPointsFound;
+    }
+
+    // Total duration assumes that the gesture starts at 0; waiting around to start a gesture
+    // counts against total duration
+    private static long getTotalDuration(List<StrokeDescription> paths) {
+        long latestEnd = Long.MIN_VALUE;
+        for (int i = 0; i < paths.size(); i++) {
+            StrokeDescription path = paths.get(i);
+            latestEnd = Math.max(latestEnd, path.mEndTime);
+        }
+        return Math.max(latestEnd, 0);
+    }
+
+    /**
+     * Builder for a {@code GestureDescription}
+     */
+    public static class Builder {
+
+        private final List<StrokeDescription> mStrokes = new ArrayList<>();
+        private int mDisplayId = Display.DEFAULT_DISPLAY;
+
+        /**
+         * Adds a stroke to the gesture description. Up to
+         * {@link GestureDescription#getMaxStrokeCount()} paths may be
+         * added to a gesture, and the total gesture duration (earliest path start time to latest
+         * path end time) may not exceed {@link GestureDescription#getMaxGestureDuration()}.
+         *
+         * @param strokeDescription the stroke to add.
+         *
+         * @return this
+         */
+        public Builder addStroke(@NonNull StrokeDescription strokeDescription) {
+            if (mStrokes.size() >= MAX_STROKE_COUNT) {
+                throw new IllegalStateException(
+                        "Attempting to add too many strokes to a gesture. Maximum is "
+                                + MAX_STROKE_COUNT
+                                + ", got "
+                                + mStrokes.size());
+            }
+
+            mStrokes.add(strokeDescription);
+
+            if (getTotalDuration(mStrokes) > MAX_GESTURE_DURATION_MS) {
+                mStrokes.remove(strokeDescription);
+                throw new IllegalStateException(
+                        "Gesture would exceed maximum duration with new stroke");
+            }
+            return this;
+        }
+
+        /**
+         * Sets the id of the display to dispatch gestures.
+         *
+         * @param displayId The logical display id
+         *
+         * @return this
+         */
+        public @NonNull Builder setDisplayId(int displayId) {
+            mDisplayId = displayId;
+            return this;
+        }
+
+        public GestureDescription build() {
+            if (mStrokes.size() == 0) {
+                throw new IllegalStateException("Gestures must have at least one stroke");
+            }
+            return new GestureDescription(mStrokes, mDisplayId);
+        }
+    }
+
+    /**
+     * Immutable description of stroke that can be part of a gesture.
+     */
+    public static class StrokeDescription {
+        private static final int INVALID_STROKE_ID = -1;
+
+        static int sIdCounter;
+
+        Path mPath;
+        long mStartTime;
+        long mEndTime;
+        private float mTimeToLengthConversion;
+        private PathMeasure mPathMeasure;
+        // The tap location is only set for zero-length paths
+        float[] mTapLocation;
+        int mId;
+        boolean mContinued;
+        int mContinuedStrokeId = INVALID_STROKE_ID;
+
+        /**
+         * @param path The path to follow. Must have exactly one contour. The bounds of the path
+         * must not be negative. The path must not be empty. If the path has zero length
+         * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
+         * @param startTime The time, in milliseconds, from the time the gesture starts to the
+         * time the stroke should start. Must not be negative.
+         * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
+         * Must be positive.
+         */
+        public StrokeDescription(@NonNull Path path,
+                @IntRange(from = 0) long startTime,
+                @IntRange(from = 0) long duration) {
+            this(path, startTime, duration, false);
+        }
+
+        /**
+         * @param path The path to follow. Must have exactly one contour. The bounds of the path
+         * must not be negative. The path must not be empty. If the path has zero length
+         * (for example, a single {@code moveTo()}), the stroke is a touch that doesn't move.
+         * @param startTime The time, in milliseconds, from the time the gesture starts to the
+         * time the stroke should start. Must not be negative.
+         * @param duration The duration, in milliseconds, the stroke takes to traverse the path.
+         * Must be positive.
+         * @param willContinue {@code true} if this stroke will be continued by one in the
+         * next gesture {@code false} otherwise. Continued strokes keep their pointers down when
+         * the gesture completes.
+         */
+        public StrokeDescription(@NonNull Path path,
+                @IntRange(from = 0) long startTime,
+                @IntRange(from = 0) long duration,
+                boolean willContinue) {
+            mContinued = willContinue;
+            Preconditions.checkArgument(duration > 0, "Duration must be positive");
+            Preconditions.checkArgument(startTime >= 0, "Start time must not be negative");
+            Preconditions.checkArgument(!path.isEmpty(), "Path is empty");
+            RectF bounds = new RectF();
+            path.computeBounds(bounds, false /* unused */);
+            Preconditions.checkArgument((bounds.bottom >= 0) && (bounds.top >= 0)
+                    && (bounds.right >= 0) && (bounds.left >= 0),
+                    "Path bounds must not be negative");
+            mPath = new Path(path);
+            mPathMeasure = new PathMeasure(path, false);
+            if (mPathMeasure.getLength() == 0) {
+                // Treat zero-length paths as taps
+                Path tempPath = new Path(path);
+                tempPath.lineTo(-1, -1);
+                mTapLocation = new float[2];
+                PathMeasure pathMeasure = new PathMeasure(tempPath, false);
+                pathMeasure.getPosTan(0, mTapLocation, null);
+            }
+            if (mPathMeasure.nextContour()) {
+                throw new IllegalArgumentException("Path has more than one contour");
+            }
+            /*
+             * Calling nextContour has moved mPathMeasure off the first contour, which is the only
+             * one we care about. Set the path again to go back to the first contour.
+             */
+            mPathMeasure.setPath(mPath, false);
+            mStartTime = startTime;
+            mEndTime = startTime + duration;
+            mTimeToLengthConversion = getLength() / duration;
+            mId = sIdCounter++;
+        }
+
+        /**
+         * Retrieve a copy of the path for this stroke
+         *
+         * @return A copy of the path
+         */
+        public Path getPath() {
+            return new Path(mPath);
+        }
+
+        /**
+         * Get the stroke's start time
+         *
+         * @return the start time for this stroke.
+         */
+        public long getStartTime() {
+            return mStartTime;
+        }
+
+        /**
+         * Get the stroke's duration
+         *
+         * @return the duration for this stroke
+         */
+        public long getDuration() {
+            return mEndTime - mStartTime;
+        }
+
+        /**
+         * Get the stroke's ID. The ID is used when a stroke is to be continued by another
+         * stroke in a future gesture.
+         *
+         * @return the ID of this stroke
+         * @hide
+         */
+        public int getId() {
+            return mId;
+        }
+
+        /**
+         * Create a new stroke that will continue this one. This is only possible if this stroke
+         * will continue.
+         *
+         * @param path The path for the stroke that continues this one. The starting point of
+         *             this path must match the ending point of the stroke it continues.
+         * @param startTime The time, in milliseconds, from the time the gesture starts to the
+         *                  time this stroke should start. Must not be negative. This time is from
+         *                  the start of the new gesture, not the one being continued.
+         * @param duration The duration for the new stroke. Must not be negative.
+         * @param willContinue {@code true} if this stroke will be continued by one in the
+         *             next gesture {@code false} otherwise.
+         * @return
+         */
+        public StrokeDescription continueStroke(Path path, long startTime, long duration,
+                boolean willContinue) {
+            if (!mContinued) {
+                throw new IllegalStateException(
+                        "Only strokes marked willContinue can be continued");
+            }
+            StrokeDescription strokeDescription =
+                    new StrokeDescription(path, startTime, duration, willContinue);
+            strokeDescription.mContinuedStrokeId = mId;
+            return strokeDescription;
+        }
+
+        /**
+         * Check if this stroke is marked to continue in the next gesture.
+         *
+         * @return {@code true} if the stroke is to be continued.
+         */
+        public boolean willContinue() {
+            return mContinued;
+        }
+
+        /**
+         * Get the ID of the stroke that this one will continue.
+         *
+         * @return The ID of the stroke that this stroke continues, or 0 if no such stroke exists.
+         * @hide
+         */
+        public int getContinuedStrokeId() {
+            return mContinuedStrokeId;
+        }
+
+        float getLength() {
+            return mPathMeasure.getLength();
+        }
+
+        /* Assumes hasPointForTime returns true */
+        boolean getPosForTime(long time, float[] pos) {
+            if (mTapLocation != null) {
+                pos[0] = mTapLocation[0];
+                pos[1] = mTapLocation[1];
+                return true;
+            }
+            if (time == mEndTime) {
+                // Close to the end time, roundoff can be a problem
+                return mPathMeasure.getPosTan(getLength(), pos, null);
+            }
+            float length = mTimeToLengthConversion * ((float) (time - mStartTime));
+            return mPathMeasure.getPosTan(length, pos, null);
+        }
+
+        boolean hasPointForTime(long time) {
+            return ((time >= mStartTime) && (time <= mEndTime));
+        }
+    }
+
+    /**
+     * The location of a finger for gesture dispatch
+     *
+     * @hide
+     */
+    public static class TouchPoint implements Parcelable {
+        private static final int FLAG_IS_START_OF_PATH = 0x01;
+        private static final int FLAG_IS_END_OF_PATH = 0x02;
+
+        public int mStrokeId;
+        public int mContinuedStrokeId;
+        public boolean mIsStartOfPath;
+        public boolean mIsEndOfPath;
+        public float mX;
+        public float mY;
+
+        public TouchPoint() {
+        }
+
+        public TouchPoint(TouchPoint pointToCopy) {
+            copyFrom(pointToCopy);
+        }
+
+        public TouchPoint(Parcel parcel) {
+            mStrokeId = parcel.readInt();
+            mContinuedStrokeId = parcel.readInt();
+            int startEnd = parcel.readInt();
+            mIsStartOfPath = (startEnd & FLAG_IS_START_OF_PATH) != 0;
+            mIsEndOfPath = (startEnd & FLAG_IS_END_OF_PATH) != 0;
+            mX = parcel.readFloat();
+            mY = parcel.readFloat();
+        }
+
+        public void copyFrom(TouchPoint other) {
+            mStrokeId = other.mStrokeId;
+            mContinuedStrokeId = other.mContinuedStrokeId;
+            mIsStartOfPath = other.mIsStartOfPath;
+            mIsEndOfPath = other.mIsEndOfPath;
+            mX = other.mX;
+            mY = other.mY;
+        }
+
+        @Override
+        public String toString() {
+            return "TouchPoint{"
+                    + "mStrokeId=" + mStrokeId
+                    + ", mContinuedStrokeId=" + mContinuedStrokeId
+                    + ", mIsStartOfPath=" + mIsStartOfPath
+                    + ", mIsEndOfPath=" + mIsEndOfPath
+                    + ", mX=" + mX
+                    + ", mY=" + mY
+                    + '}';
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel dest, int flags) {
+            dest.writeInt(mStrokeId);
+            dest.writeInt(mContinuedStrokeId);
+            int startEnd = mIsStartOfPath ? FLAG_IS_START_OF_PATH : 0;
+            startEnd |= mIsEndOfPath ? FLAG_IS_END_OF_PATH : 0;
+            dest.writeInt(startEnd);
+            dest.writeFloat(mX);
+            dest.writeFloat(mY);
+        }
+
+        public static final @android.annotation.NonNull Parcelable.Creator<TouchPoint> CREATOR
+                = new Parcelable.Creator<TouchPoint>() {
+            public TouchPoint createFromParcel(Parcel in) {
+                return new TouchPoint(in);
+            }
+
+            public TouchPoint[] newArray(int size) {
+                return new TouchPoint[size];
+            }
+        };
+    }
+
+    /**
+     * A step along a gesture. Contains all of the touch points at a particular time
+     *
+     * @hide
+     */
+    public static class GestureStep implements Parcelable {
+        public long timeSinceGestureStart;
+        public int numTouchPoints;
+        public TouchPoint[] touchPoints;
+
+        public GestureStep(long timeSinceGestureStart, int numTouchPoints,
+                TouchPoint[] touchPointsToCopy) {
+            this.timeSinceGestureStart = timeSinceGestureStart;
+            this.numTouchPoints = numTouchPoints;
+            this.touchPoints = new TouchPoint[numTouchPoints];
+            for (int i = 0; i < numTouchPoints; i++) {
+                this.touchPoints[i] = new TouchPoint(touchPointsToCopy[i]);
+            }
+        }
+
+        public GestureStep(Parcel parcel) {
+            timeSinceGestureStart = parcel.readLong();
+            Parcelable[] parcelables =
+                    parcel.readParcelableArray(TouchPoint.class.getClassLoader());
+            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/accessibilityservice/util/AccessibilityUtils.java b/android/accessibilityservice/util/AccessibilityUtils.java
new file mode 100644
index 0000000..fa32bb2
--- /dev/null
+++ b/android/accessibilityservice/util/AccessibilityUtils.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accessibilityservice.util;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.StringRes;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.util.TypedValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.regex.Pattern;
+
+/**
+ * Collection of utilities for accessibility service.
+ *
+ * @hide
+ */
+public final class AccessibilityUtils {
+    private AccessibilityUtils() {}
+
+    // Used for html description of accessibility service. The <img> src tag must follow the
+    // prefix rule. e.g. <img src="R.drawable.fileName"/>
+    private static final String IMG_PREFIX = "R.drawable.";
+    private static final String ANCHOR_TAG = "a";
+    private static final List<String> UNSUPPORTED_TAG_LIST = new ArrayList<>(
+            Collections.singletonList(ANCHOR_TAG));
+
+    /**
+     * Gets the filtered html string for
+     * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+     * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It filters
+     * the <img> tag which do not meet the custom specification and the <a> tag.
+     *
+     * @param text the target text is html format.
+     * @return the filtered html string.
+     */
+    public static @NonNull String getFilteredHtmlText(@NonNull String text) {
+        final String replacementStart = "<invalidtag ";
+        final String replacementEnd = "</invalidtag>";
+
+        for (String tag : UNSUPPORTED_TAG_LIST) {
+            final String regexStart = "(?i)<" + tag + "(\\s+|>)";
+            final String regexEnd = "(?i)</" + tag + "\\s*>";
+            text = Pattern.compile(regexStart).matcher(text).replaceAll(replacementStart);
+            text = Pattern.compile(regexEnd).matcher(text).replaceAll(replacementEnd);
+        }
+
+        final String regexInvalidImgTag = "(?i)<img\\s+(?!src\\s*=\\s*\"(?-i)" + IMG_PREFIX + ")";
+        text = Pattern.compile(regexInvalidImgTag).matcher(text).replaceAll(
+                replacementStart);
+
+        return text;
+    }
+
+    /**
+     * Loads the animated image for
+     * {@link android.accessibilityservice.AccessibilityServiceInfo} and
+     * {@link android.accessibilityservice.AccessibilityShortcutInfo}. It checks the resource
+     * whether to exceed the screen size.
+     *
+     * @param context the current context.
+     * @param applicationInfo the current application.
+     * @param resId the animated image resource id.
+     * @return the animated image which is safe.
+     */
+    @Nullable
+    public static Drawable loadSafeAnimatedImage(@NonNull Context context,
+            @NonNull ApplicationInfo applicationInfo, @StringRes int resId) {
+        if (resId == /* invalid */ 0) {
+            return null;
+        }
+
+        final PackageManager packageManager = context.getPackageManager();
+        final String packageName = applicationInfo.packageName;
+        final Drawable bannerDrawable = packageManager.getDrawable(packageName, resId,
+                applicationInfo);
+        if (bannerDrawable == null) {
+            return null;
+        }
+
+        final boolean isImageWidthOverScreenLength =
+                bannerDrawable.getIntrinsicWidth() > getScreenWidthPixels(context);
+        final boolean isImageHeightOverScreenLength =
+                bannerDrawable.getIntrinsicHeight() > getScreenHeightPixels(context);
+
+        return (isImageWidthOverScreenLength || isImageHeightOverScreenLength)
+                ? null
+                : bannerDrawable;
+    }
+
+    /**
+     * Gets the width of the screen.
+     *
+     * @param context the current context.
+     * @return the width of the screen in term of pixels.
+     */
+    private static int getScreenWidthPixels(@NonNull Context context) {
+        final Resources resources = context.getResources();
+        final int screenWidthDp = resources.getConfiguration().screenWidthDp;
+
+        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenWidthDp,
+                resources.getDisplayMetrics()));
+    }
+
+    /**
+     * Gets the height of the screen.
+     *
+     * @param context the current context.
+     * @return the height of the screen in term of pixels.
+     */
+    private static int getScreenHeightPixels(@NonNull Context context) {
+        final Resources resources = context.getResources();
+        final int screenHeightDp = resources.getConfiguration().screenHeightDp;
+
+        return Math.round(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, screenHeightDp,
+                resources.getDisplayMetrics()));
+    }
+}
diff --git a/android/accounts/AbstractAccountAuthenticator.java b/android/accounts/AbstractAccountAuthenticator.java
new file mode 100644
index 0000000..6470a04
--- /dev/null
+++ b/android/accounts/AbstractAccountAuthenticator.java
@@ -0,0 +1,995 @@
+/*
+ * 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).
+ */
+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..9a18880
--- /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.compat.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..fd67394
--- /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.compat.annotation.UnsupportedAppUsage;
+
+/**
+ * Used to store the Account and the UserId this account is associated with.
+ *
+ * @hide
+ */
+public class AccountAndUser {
+    @UnsupportedAppUsage
+    public Account account;
+    @UnsupportedAppUsage
+    public int userId;
+
+    @UnsupportedAppUsage
+    public AccountAndUser(Account account, int userId) {
+        this.account = account;
+        this.userId = userId;
+    }
+
+    public boolean equals(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..65ba35f
--- /dev/null
+++ b/android/accounts/AccountAuthenticatorActivity.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.accounts;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * Base class for implementing an Activity that is used to help implement an
+ * AbstractAccountAuthenticator. If the AbstractAccountAuthenticator needs to use an activity
+ * to handle the request then it can have the activity extend AccountAuthenticatorActivity.
+ * The AbstractAccountAuthenticator passes in the response to the intent using the following:
+ * <pre>
+ *      intent.putExtra({@link AccountManager#KEY_ACCOUNT_AUTHENTICATOR_RESPONSE}, response);
+ * </pre>
+ * The activity then sets the result that is to be handed to the response via
+ * {@link #setAccountAuthenticatorResult(android.os.Bundle)}.
+ * This result will be sent as the result of the request when the activity finishes. If this
+ * is never set or if it is set to null then error {@link AccountManager#ERROR_CODE_CANCELED}
+ * will be called on the response.
+ *
+ * @deprecated Applications should extend Activity themselves. This class is not compatible with
+ *   AppCompat, and the functionality it provides is not complex.
+ */
+@Deprecated
+public class AccountAuthenticatorActivity extends Activity {
+    private AccountAuthenticatorResponse mAccountAuthenticatorResponse = null;
+    private Bundle mResultBundle = null;
+
+    /**
+     * Set the result that is to be sent as the result of the request that caused this
+     * Activity to be launched. If result is null or this method is never called then
+     * the request will be canceled.
+     * @param result this is returned as the result of the AbstractAccountAuthenticator request
+     */
+    public final void setAccountAuthenticatorResult(Bundle result) {
+        mResultBundle = result;
+    }
+
+    /**
+     * Retrieves the AccountAuthenticatorResponse from either the intent of the icicle, if the
+     * icicle is non-zero.
+     * @param icicle the save instance data of this Activity, may be null
+     */
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mAccountAuthenticatorResponse =
+                getIntent().getParcelableExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE);
+
+        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..a2a5799
--- /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.compat.annotation.UnsupportedAppUsage;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.util.Log;
+
+/**
+ * Object used to communicate responses back to the AccountManager
+ */
+public class AccountAuthenticatorResponse implements Parcelable {
+    private static final String TAG = "AccountAuthenticator";
+
+    private IAccountAuthenticatorResponse mAccountAuthenticatorResponse;
+
+    /**
+     * @hide
+     */
+    @UnsupportedAppUsage
+    public AccountAuthenticatorResponse(IAccountAuthenticatorResponse response) {
+        mAccountAuthenticatorResponse = response;
+    }
+
+    public AccountAuthenticatorResponse(Parcel parcel) {
+        mAccountAuthenticatorResponse =
+                IAccountAuthenticatorResponse.Stub.asInterface(parcel.readStrongBinder());
+    }
+
+    public void onResult(Bundle result) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            result.keySet(); // force it to be unparcelled
+            Log.v(TAG, "AccountAuthenticatorResponse.onResult: "
+                    + AccountManager.sanitizeResult(result));
+        }
+        try {
+            mAccountAuthenticatorResponse.onResult(result);
+        } catch (RemoteException e) {
+            // this should never happen
+        }
+    }
+
+    public void onRequestContinued() {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "AccountAuthenticatorResponse.onRequestContinued");
+        }
+        try {
+            mAccountAuthenticatorResponse.onRequestContinued();
+        } catch (RemoteException e) {
+            // this should never happen
+        }
+    }
+
+    public void onError(int errorCode, String errorMessage) {
+        if (Log.isLoggable(TAG, Log.VERBOSE)) {
+            Log.v(TAG, "AccountAuthenticatorResponse.onError: " + errorCode + ", " + errorMessage);
+        }
+        try {
+            mAccountAuthenticatorResponse.onError(errorCode, errorMessage);
+        } catch (RemoteException e) {
+            // this should never happen
+        }
+    }
+
+    public int describeContents() {
+        return 0;
+    }
+
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStrongBinder(mAccountAuthenticatorResponse.asBinder());
+    }
+
+    public static final @android.annotation.NonNull Creator<AccountAuthenticatorResponse> CREATOR =
+            new Creator<AccountAuthenticatorResponse>() {
+        public AccountAuthenticatorResponse createFromParcel(Parcel source) {
+            return new AccountAuthenticatorResponse(source);
+        }
+
+        public AccountAuthenticatorResponse[] newArray(int size) {
+            return new AccountAuthenticatorResponse[size];
+        }
+    };
+}
diff --git a/android/accounts/AccountManager.java b/android/accounts/AccountManager.java
new file mode 100644
index 0000000..f2702a8
--- /dev/null
+++ b/android/accounts/AccountManager.java
@@ -0,0 +1,3336 @@
+/*
+ * 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.UserHandleAware;
+import android.app.Activity;
+import android.compat.annotation.UnsupportedAppUsage;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.IntentSender;
+import android.content.res.Resources;
+import android.database.SQLException;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Parcelable;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.UserHandle;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.R;
+
+import com.google.android.collect.Maps;
+
+import java.io.IOException;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.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.
+     */
+    @UserHandleAware
+    public AuthenticatorDescription[] getAuthenticatorTypes() {
+        return getAuthenticatorTypesAsUser(mContext.getUserId());
+    }
+
+    /**
+     * @hide
+     * Lists the currently registered authenticators for a given user id.
+     *
+     * <p>It is safe to call this method from the main thread.
+     *
+     * <p>The caller has to be in the same user or have the permission
+     * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL}.
+     *
+     * @return An array of {@link AuthenticatorDescription} for every
+     *     authenticator known to the AccountManager service.  Empty (never
+     *     null) if no authenticators are known.
+     */
+    public AuthenticatorDescription[] getAuthenticatorTypesAsUser(int userId) {
+        try {
+            return mService.getAuthenticatorTypes(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Lists all accounts visible to the caller regardless of type. Equivalent to
+     * getAccountsByType(null). These accounts may be visible because the user granted access to the
+     * account, or the AbstractAcccountAuthenticator managing the account did so or because the
+     * client shares a signature with the managing AbstractAccountAuthenticator.
+     *
+     * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+     * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+     * disclose that fact to users. For apps published on Google Play, policies protecting user data
+     * require that you do the following:</p>
+     * <ul>
+     * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+     * sensitive data. Learn more about
+     * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+     * disclosure and consent</a>.</li>
+     * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+     * </ul>
+     * <p>To learn more, visit the
+     * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+     * Policy regarding user data</a>.</p></div>
+     *
+     * <p>
+     * It is safe to call this method from the main thread.
+     *
+     * @return An array of {@link Account}, one for each account. Empty (never null) if no accounts
+     *         have been added.
+     */
+    @UserHandleAware
+    @NonNull
+    public Account[] getAccounts() {
+        return getAccountsAsUser(mContext.getUserId());
+    }
+
+    /**
+     * @hide
+     * Lists all accounts visible to caller regardless of type for a given user id. Equivalent to
+     * getAccountsByType(null).
+     *
+     * <p>
+     * It is safe to call this method from the main thread.
+     *
+     * @return An array of {@link Account}, one for each account. Empty (never null) if no accounts
+     *         have been added.
+     */
+    @NonNull
+    public Account[] getAccountsAsUser(int userId) {
+        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>
+     *
+     * <div class="caution"><p><b>Caution: </b>This method returns personal and sensitive user data.
+     * If your app accesses, collects, uses, or shares personal and sensitive data, you must clearly
+     * disclose that fact to users. For apps published on Google Play, policies protecting user data
+     * require that you do the following:</p>
+     * <ul>
+     * <li>Disclose to the user how your app accesses, collects, uses, or shares personal and
+     * sensitive data. Learn more about
+     * <a href="https://play.google.com/about/privacy-security-deception/user-data/#!#personal-sensitive">acceptable
+     * disclosure and consent</a>.</li>
+     * <li>Provide a privacy policy that describes your use of this data on- and off-device.</li>
+     * </ul>
+     * <p>To learn more, visit the
+     * <a href="https://play.google.com/about/privacy-security-deception/user-data">Google Play
+     * Policy regarding user data</a>.</p></div>
+     *
+     * <p>
+     * It is safe to call this method from the main thread.
+     *
+     * <p>
+     * Caller targeting API level {@link android.os.Build.VERSION_CODES#O} and above, will get list
+     * of accounts made visible to it by user
+     * (see {@link #newChooseAccountIntent(Account, List, String[], String,
+     * String, String[], Bundle)}) or 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.
+     */
+    @UserHandleAware
+    @NonNull
+    public Account[] getAccountsByType(String type) {
+        return getAccountsByTypeAsUser(type, mContext.getUser());
+    }
+
+    /** @hide Same as {@link #getAccountsByType(String)} but for a specific user. */
+    @NonNull
+    @UnsupportedAppUsage
+    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
+     */
+    @UserHandleAware
+    @Deprecated
+    public AccountManagerFuture<Boolean> removeAccount(final Account account,
+            AccountManagerCallback<Boolean> callback, Handler handler) {
+        return removeAccountAsUser(account, callback, handler, mContext.getUser());
+    }
+
+    /**
+     * Removes an account from the AccountManager. Does nothing if the account
+     * does not exist.  Does not delete the account from the server.
+     * The authenticator may have its own policies preventing account
+     * deletion, in which case the account will not be deleted.
+     *
+     * <p>This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     *
+     * <p>This method requires the caller to have a signature match with the
+     * authenticator that manages the specified account.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+     * this function in API level 22.
+     *
+     * @param account The {@link Account} to remove
+     * @param activity The {@link Activity} context to use for launching a new
+     *     authenticator-defined sub-Activity to prompt the user to delete an
+     *     account; used only to call startActivity(); if null, the prompt
+     *     will not be launched directly, but the {@link Intent} may be
+     *     returned to the caller instead
+     * @param callback Callback to invoke when the request completes,
+     *     null for no callback
+     * @param handler {@link Handler} identifying the callback thread,
+     *     null for the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+     *     {@link #KEY_BOOLEAN_RESULT} if activity was specified and an account
+     *     was removed or if active. If no activity was specified, the returned
+     *     Bundle contains only {@link #KEY_INTENT} with the {@link Intent}
+     *     needed to launch the actual account removal process, if authenticator
+     *     needs the activity launch. If an error occurred,
+     *     {@link AccountManagerFuture#getResult()} throws:
+     * <ul>
+     * <li> {@link AuthenticatorException} if no authenticator was registered for
+     *      this account type or the authenticator failed to respond
+     * <li> {@link OperationCanceledException} if the operation was canceled for
+     *      any reason, including the user canceling the creation process or
+     *      adding accounts (of this type) has been disabled by policy
+     * </ul>
+     */
+    @UserHandleAware
+    public AccountManagerFuture<Bundle> removeAccount(final Account account,
+            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+        return removeAccountAsUser(account, activity, callback, handler, mContext.getUser());
+    }
+
+    /**
+     * @see #removeAccount(Account, AccountManagerCallback, Handler)
+     * @hide
+     * @deprecated use
+     *     {@link #removeAccountAsUser(Account, Activity, AccountManagerCallback, Handler)}
+     *     instead
+     */
+    @Deprecated
+    public AccountManagerFuture<Boolean> removeAccountAsUser(final Account account,
+            AccountManagerCallback<Boolean> callback, Handler handler,
+            final UserHandle userHandle) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
+        return new Future2Task<Boolean>(handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.removeAccountAsUser(mResponse, account, false, userHandle.getIdentifier());
+            }
+            @Override
+            public Boolean bundleToResult(Bundle bundle) throws AuthenticatorException {
+                if (!bundle.containsKey(KEY_BOOLEAN_RESULT)) {
+                    throw new AuthenticatorException("no result in response");
+                }
+                return bundle.getBoolean(KEY_BOOLEAN_RESULT);
+            }
+        }.start();
+    }
+
+    /**
+     * @see #removeAccount(Account, Activity, AccountManagerCallback, Handler)
+     * @hide
+     */
+    public AccountManagerFuture<Bundle> removeAccountAsUser(final Account account,
+            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler,
+            final UserHandle userHandle) {
+        if (account == null)
+            throw new IllegalArgumentException("account is null");
+        if (userHandle == null)
+            throw new IllegalArgumentException("userHandle is null");
+        return new AmsTask(activity, handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.removeAccountAsUser(mResponse, account, activity != null,
+                        userHandle.getIdentifier());
+            }
+        }.start();
+    }
+
+    /**
+     * Removes an account directly. Normally used by authenticators, not
+     * directly by applications. Does not delete the account from the server.
+     * The authenticator may have its own policies preventing account deletion,
+     * in which case the account will not be deleted.
+     * <p>
+     * It is safe to call this method from the main thread.
+     * <p>This method requires the caller to have a signature match with the
+     * authenticator that manages the specified account.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+     * is needed for those platforms. See docs for this function in API level 22.
+     *
+     * @param account The {@link Account} to delete.
+     * @return True if the account was successfully deleted, false if the
+     *         account did not exist, the account is null, or another error
+     *         occurs.
+     */
+    public boolean removeAccountExplicitly(Account account) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        try {
+            return mService.removeAccountExplicitly(account);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Removes an auth token from the AccountManager's cache.  Does nothing if
+     * the auth token is not currently in the cache.  Applications must call this
+     * method when the auth token is found to have expired or otherwise become
+     * invalid for authenticating requests.  The AccountManager does not validate
+     * or expire cached auth tokens otherwise.
+     *
+     * <p>It is safe to call this method from the main thread.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * MANAGE_ACCOUNTS or USE_CREDENTIALS permission is needed for those
+     * platforms. See docs for this function in API level 22.
+     *
+     * @param accountType The account type of the auth token to invalidate, must not be null
+     * @param authToken The auth token to invalidate, may be null
+     */
+    public void invalidateAuthToken(final String accountType, final String authToken) {
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        try {
+            if (authToken != null) {
+                mService.invalidateAuthToken(accountType, authToken);
+            }
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Gets an auth token from the AccountManager's cache.  If no auth
+     * token is cached for this account, null will be returned -- a new
+     * auth token will not be generated, and the server will not be contacted.
+     * Intended for use by the authenticator, not directly by applications.
+     *
+     * <p>It is safe to call this method from the main thread.
+     *
+     * <p>This method requires the caller to have a signature match with the
+     * authenticator that manages the specified account.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+     * is needed for those platforms. See docs for this function in API level 22.
+     *
+     * @param account The account for which an auth token is to be fetched. Cannot be {@code null}.
+     * @param authTokenType The type of auth token to fetch. Cannot be {@code null}.
+     * @return The cached auth token for this account and type, or null if
+     *     no auth token is cached, the account does not exist, or the user is locked
+     * @see #getAuthToken
+     */
+    public String peekAuthToken(final Account account, final String authTokenType) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        try {
+            return mService.peekAuthToken(account, authTokenType);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets or forgets a saved password. This modifies the local copy of the
+     * password used to automatically authenticate the user; it does not change
+     * the user's account password on the server. Intended for use by the
+     * authenticator, not directly by applications.
+     * <p>Calling this method does not update the last authenticated timestamp,
+     * referred by {@link #KEY_LAST_AUTHENTICATED_TIME}. To update it, call
+     * {@link #notifyAccountAuthenticated(Account)} after getting success.
+     * <p>It is safe to call this method from the main thread.
+     * <p>This method requires the caller to have a signature match with the
+     * authenticator that manages the specified account.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+     * is needed for those platforms. See docs for this function in API level 22.
+     *
+     * @param account The account whose password is to be set. Cannot be
+     *            {@code null}.
+     * @param password The password to set, null to clear the password
+     */
+    public void setPassword(final Account account, final String password) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        try {
+            mService.setPassword(account, password);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Forgets a saved password.  This erases the local copy of the password;
+     * it does not change the user's account password on the server.
+     * Has the same effect as setPassword(account, null) but requires fewer
+     * permissions, and may be used by applications or management interfaces
+     * to "sign out" from an account.
+     *
+     * <p>This method only successfully clear the account's password when the
+     * caller has the same signature as the authenticator that owns the
+     * specified account. Otherwise, this method will silently fail.
+     *
+     * <p>It is safe to call this method from the main thread.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+     * this function in API level 22.
+     *
+     * @param account The account whose password to clear
+     */
+    public void clearPassword(final Account account) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        try {
+            mService.clearPassword(account);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Sets one userdata key for an account. Intended by use for the
+     * authenticator to stash state for itself, not directly by applications.
+     * The meaning of the keys and values is up to the authenticator.
+     *
+     * <p>It is safe to call this method from the main thread.
+     *
+     * <p>This method requires the caller to have a signature match with the
+     * authenticator that manages the specified account.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+     * is needed for those platforms. See docs for this function in API level 22.
+     *
+     * @param account Account whose user data is to be set. Must not be {@code null}.
+     * @param key String user data key to set.  Must not be null
+     * @param value String value to set, {@code null} to clear this user data key
+     */
+    public void setUserData(final Account account, final String key, final String value) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (key == null) throw new IllegalArgumentException("key is null");
+        try {
+            mService.setUserData(account, key, value);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Adds an auth token to the AccountManager cache for an account.
+     * If the account does not exist then this call has no effect.
+     * Replaces any previous auth token for this account and auth token type.
+     * Intended for use by the authenticator, not directly by applications.
+     *
+     * <p>It is safe to call this method from the main thread.
+     *
+     * <p>This method requires the caller to have a signature match with the
+     * authenticator that manages the specified account.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * AUTHENTICATE_ACCOUNTS permission and same UID as account's authenticator
+     * is needed for those platforms. See docs for this function in API level 22.
+     *
+     * @param account The account to set an auth token for
+     * @param authTokenType The type of the auth token, see {#getAuthToken}
+     * @param authToken The auth token to add to the cache
+     */
+    public void setAuthToken(Account account, final String authTokenType, final String authToken) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        try {
+            mService.setAuthToken(account, authTokenType, authToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * This convenience helper synchronously gets an auth token with
+     * {@link #getAuthToken(Account, String, boolean, AccountManagerCallback, Handler)}.
+     *
+     * <p>This method may block while a network request completes, and must
+     * never be made from the main thread.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * USE_CREDENTIALS permission is needed for those platforms. See docs for
+     * this function in API level 22.
+     *
+     * @param account The account to fetch an auth token for
+     * @param authTokenType The auth token type, see {@link #getAuthToken getAuthToken()}
+     * @param notifyAuthFailure If true, display a notification and return null
+     *     if authentication fails; if false, prompt and wait for the user to
+     *     re-enter correct credentials before returning
+     * @return An auth token of the specified type for this account, or null
+     *     if authentication fails or none can be fetched.
+     * @throws AuthenticatorException if the authenticator failed to respond
+     * @throws OperationCanceledException if the request was canceled for any
+     *     reason, including the user canceling a credential request
+     * @throws java.io.IOException if the authenticator experienced an I/O problem
+     *     creating a new auth token, usually because of network trouble
+     */
+    public String blockingGetAuthToken(Account account, String authTokenType,
+            boolean notifyAuthFailure)
+            throws OperationCanceledException, IOException, AuthenticatorException {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        Bundle bundle = getAuthToken(account, authTokenType, notifyAuthFailure, null /* callback */,
+                null /* handler */).getResult();
+        if (bundle == null) {
+            // This should never happen, but it does, occasionally. If it does return null to
+            // signify that we were not able to get the authtoken.
+            // TODO: remove this when the bug is found that sometimes causes a null bundle to be
+            // returned
+            Log.e(TAG, "blockingGetAuthToken: null was returned from getResult() for "
+                    + account + ", authTokenType " + authTokenType);
+            return null;
+        }
+        return bundle.getString(KEY_AUTHTOKEN);
+    }
+
+    /**
+     * Gets an auth token of the specified type for a particular account,
+     * prompting the user for credentials if necessary.  This method is
+     * intended for applications running in the foreground where it makes
+     * sense to ask the user directly for a password.
+     *
+     * <p>If a previously generated auth token is cached for this account and
+     * type, then it is returned.  Otherwise, if a saved password is
+     * available, it is sent to the server to generate a new auth token.
+     * Otherwise, the user is prompted to enter a password.
+     *
+     * <p>Some authenticators have auth token <em>types</em>, whose value
+     * is authenticator-dependent.  Some services use different token types to
+     * access different functionality -- for example, Google uses different auth
+     * tokens to access Gmail and Google Calendar for the same account.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * USE_CREDENTIALS permission is needed for those platforms. See docs for
+     * this function in API level 22.
+     *
+     * <p>This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     *
+     * @param account The account to fetch an auth token for
+     * @param authTokenType The auth token type, an authenticator-dependent
+     *     string token, must not be null
+     * @param options Authenticator-specific options for the request,
+     *     may be null or empty
+     * @param activity The {@link Activity} context to use for launching a new
+     *     authenticator-defined sub-Activity to prompt the user for a password
+     *     if necessary; used only to call startActivity(); must not be null.
+     * @param callback Callback to invoke when the request completes,
+     *     null for no callback
+     * @param handler {@link Handler} identifying the callback thread,
+     *     null for the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+     *     at least the following fields:
+     * <ul>
+     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+     * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+     * </ul>
+     *
+     * (Other authenticator-specific values may be returned.)  If an auth token
+     * could not be fetched, {@link AccountManagerFuture#getResult()} throws:
+     * <ul>
+     * <li> {@link AuthenticatorException} if the authenticator failed to respond
+     * <li> {@link OperationCanceledException} if the operation is canceled for
+     *      any reason, incluidng the user canceling a credential request
+     * <li> {@link IOException} if the authenticator experienced an I/O problem
+     *      creating a new auth token, usually because of network trouble
+     * </ul>
+     * If the account is no longer present on the device, the return value is
+     * authenticator-dependent.  The caller should verify the validity of the
+     * account before requesting an auth token.
+     */
+    public AccountManagerFuture<Bundle> getAuthToken(
+            final Account account, final String authTokenType, final Bundle options,
+            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        final Bundle optionsIn = new Bundle();
+        if (options != null) {
+            optionsIn.putAll(options);
+        }
+        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+        return new AmsTask(activity, handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.getAuthToken(mResponse, account, authTokenType,
+                        false /* notifyOnAuthFailure */, true /* expectActivityLaunch */,
+                        optionsIn);
+            }
+        }.start();
+    }
+
+    /**
+     * Gets an auth token of the specified type for a particular account,
+     * optionally raising a notification if the user must enter credentials.
+     * This method is intended for background tasks and services where the
+     * user should not be immediately interrupted with a password prompt.
+     *
+     * <p>If a previously generated auth token is cached for this account and
+     * type, then it is returned.  Otherwise, if a saved password is
+     * available, it is sent to the server to generate a new auth token.
+     * Otherwise, an {@link Intent} is returned which, when started, will
+     * prompt the user for a password.  If the notifyAuthFailure parameter is
+     * set, a status bar notification is also created with the same Intent,
+     * alerting the user that they need to enter a password at some point.
+     *
+     * <p>In that case, you may need to wait until the user responds, which
+     * could take hours or days or forever.  When the user does respond and
+     * supply a new password, the account manager will broadcast the
+     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+     * notify {@link OnAccountsUpdateListener} which applications can
+     * use to try again.
+     *
+     * <p>If notifyAuthFailure is not set, it is the application's
+     * responsibility to launch the returned Intent at some point.
+     * Either way, the result from this call will not wait for user action.
+     *
+     * <p>Some authenticators have auth token <em>types</em>, whose value
+     * is authenticator-dependent.  Some services use different token types to
+     * access different functionality -- for example, Google uses different auth
+     * tokens to access Gmail and Google Calendar for the same account.
+     *
+     * <p>This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     *
+     * @param account The account to fetch an auth token for
+     * @param authTokenType The auth token type, an authenticator-dependent
+     *     string token, must not be null
+     * @param notifyAuthFailure True to add a notification to prompt the
+     *     user for a password if necessary, false to leave that to the caller
+     * @param callback Callback to invoke when the request completes,
+     *     null for no callback
+     * @param handler {@link Handler} identifying the callback thread,
+     *     null for the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+     *     at least the following fields on success:
+     * <ul>
+     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+     * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+     * </ul>
+     *
+     * (Other authenticator-specific values may be returned.)  If the user
+     * must enter credentials, the returned Bundle contains only
+     * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
+     *
+     * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+     * <ul>
+     * <li> {@link AuthenticatorException} if the authenticator failed to respond
+     * <li> {@link OperationCanceledException} if the operation is canceled for
+     *      any reason, incluidng the user canceling a credential request
+     * <li> {@link IOException} if the authenticator experienced an I/O problem
+     *      creating a new auth token, usually because of network trouble
+     * </ul>
+     * If the account is no longer present on the device, the return value is
+     * authenticator-dependent.  The caller should verify the validity of the
+     * account before requesting an auth token.
+     * @deprecated use {@link #getAuthToken(Account, String, android.os.Bundle,
+     * boolean, AccountManagerCallback, android.os.Handler)} instead
+     */
+    @Deprecated
+    public AccountManagerFuture<Bundle> getAuthToken(
+            final Account account, final String authTokenType,
+            final boolean notifyAuthFailure,
+            AccountManagerCallback<Bundle> callback, Handler handler) {
+        return getAuthToken(account, authTokenType, null, notifyAuthFailure, callback,
+                handler);
+    }
+
+    /**
+     * Gets an auth token of the specified type for a particular account,
+     * optionally raising a notification if the user must enter credentials.
+     * This method is intended for background tasks and services where the
+     * user should not be immediately interrupted with a password prompt.
+     *
+     * <p>If a previously generated auth token is cached for this account and
+     * type, then it is returned.  Otherwise, if a saved password is
+     * available, it is sent to the server to generate a new auth token.
+     * Otherwise, an {@link Intent} is returned which, when started, will
+     * prompt the user for a password.  If the notifyAuthFailure parameter is
+     * set, a status bar notification is also created with the same Intent,
+     * alerting the user that they need to enter a password at some point.
+     *
+     * <p>In that case, you may need to wait until the user responds, which
+     * could take hours or days or forever.  When the user does respond and
+     * supply a new password, the account manager will broadcast the
+     * {@link #LOGIN_ACCOUNTS_CHANGED_ACTION} Intent and
+     * notify {@link OnAccountsUpdateListener} which applications can
+     * use to try again.
+     *
+     * <p>If notifyAuthFailure is not set, it is the application's
+     * responsibility to launch the returned Intent at some point.
+     * Either way, the result from this call will not wait for user action.
+     *
+     * <p>Some authenticators have auth token <em>types</em>, whose value
+     * is authenticator-dependent.  Some services use different token types to
+     * access different functionality -- for example, Google uses different auth
+     * tokens to access Gmail and Google Calendar for the same account.
+     *
+     * <p>This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * USE_CREDENTIALS permission is needed for those platforms. See docs for
+     * this function in API level 22.
+     *
+     * @param account The account to fetch an auth token for
+     * @param authTokenType The auth token type, an authenticator-dependent
+     *     string token, must not be null
+     * @param options Authenticator-specific options for the request,
+     *     may be null or empty
+     * @param notifyAuthFailure True to add a notification to prompt the
+     *     user for a password if necessary, false to leave that to the caller
+     * @param callback Callback to invoke when the request completes,
+     *     null for no callback
+     * @param handler {@link Handler} identifying the callback thread,
+     *     null for the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+     *     at least the following fields on success:
+     * <ul>
+     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account you supplied
+     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+     * <li> {@link #KEY_AUTHTOKEN} - the auth token you wanted
+     * </ul>
+     *
+     * (Other authenticator-specific values may be returned.)  If the user
+     * must enter credentials, the returned Bundle contains only
+     * {@link #KEY_INTENT} with the {@link Intent} needed to launch a prompt.
+     *
+     * If an error occurred, {@link AccountManagerFuture#getResult()} throws:
+     * <ul>
+     * <li> {@link AuthenticatorException} if the authenticator failed to respond
+     * <li> {@link OperationCanceledException} if the operation is canceled for
+     *      any reason, incluidng the user canceling a credential request
+     * <li> {@link IOException} if the authenticator experienced an I/O problem
+     *      creating a new auth token, usually because of network trouble
+     * </ul>
+     * If the account is no longer present on the device, the return value is
+     * authenticator-dependent.  The caller should verify the validity of the
+     * account before requesting an auth token.
+     */
+    public AccountManagerFuture<Bundle> getAuthToken(
+            final Account account, final String authTokenType, final Bundle options,
+            final boolean notifyAuthFailure,
+            AccountManagerCallback<Bundle> callback, Handler handler) {
+
+        if (account == null) throw new IllegalArgumentException("account is null");
+        if (authTokenType == null) throw new IllegalArgumentException("authTokenType is null");
+        final Bundle optionsIn = new Bundle();
+        if (options != null) {
+            optionsIn.putAll(options);
+        }
+        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+        return new AmsTask(null, handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.getAuthToken(mResponse, account, authTokenType,
+                        notifyAuthFailure, false /* expectActivityLaunch */, optionsIn);
+            }
+        }.start();
+    }
+
+    /**
+     * Asks the user to add an account of a specified type.  The authenticator
+     * for this account type processes this request with the appropriate user
+     * interface.  If the user does elect to create a new account, the account
+     * name is returned.
+     *
+     * <p>This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * MANAGE_ACCOUNTS permission is needed for those platforms. See docs for
+     * this function in API level 22.
+     *
+     * @param accountType The type of account to add; must not be null
+     * @param authTokenType The type of auth token (see {@link #getAuthToken})
+     *     this account will need to be able to generate, null for none
+     * @param requiredFeatures The features (see {@link #hasFeatures}) this
+     *     account must have, null for none
+     * @param addAccountOptions Authenticator-specific options for the request,
+     *     may be null or empty
+     * @param activity The {@link Activity} context to use for launching a new
+     *     authenticator-defined sub-Activity to prompt the user to create an
+     *     account; used only to call startActivity(); if null, the prompt
+     *     will not be launched directly, but the necessary {@link Intent}
+     *     will be returned to the caller instead
+     * @param callback Callback to invoke when the request completes,
+     *     null for no callback
+     * @param handler {@link Handler} identifying the callback thread,
+     *     null for the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Bundle with
+     *     these fields if activity was specified and an account was created:
+     * <ul>
+     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+     * </ul>
+     *
+     * If no activity was specified, the returned Bundle contains only
+     * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+     * actual account creation process.  If an error occurred,
+     * {@link AccountManagerFuture#getResult()} throws:
+     * <ul>
+     * <li> {@link AuthenticatorException} if no authenticator was registered for
+     *      this account type or the authenticator failed to respond
+     * <li> {@link OperationCanceledException} if the operation was canceled for
+     *      any reason, including the user canceling the creation process or adding accounts
+     *      (of this type) has been disabled by policy
+     * <li> {@link IOException} if the authenticator experienced an I/O problem
+     *      creating a new account, usually because of network trouble
+     * </ul>
+     */
+    @UserHandleAware
+    public AccountManagerFuture<Bundle> addAccount(final String accountType,
+            final String authTokenType, final String[] requiredFeatures,
+            final Bundle addAccountOptions,
+            final Activity activity, AccountManagerCallback<Bundle> callback, Handler handler) {
+        if (Process.myUserHandle().equals(mContext.getUser())) {
+            if (accountType == null) throw new IllegalArgumentException("accountType is null");
+            final Bundle optionsIn = new Bundle();
+            if (addAccountOptions != null) {
+                optionsIn.putAll(addAccountOptions);
+            }
+            optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+            return new AmsTask(activity, handler, callback) {
+                @Override
+                public void doWork() throws RemoteException {
+                    mService.addAccount(mResponse, accountType, authTokenType,
+                            requiredFeatures, activity != null, optionsIn);
+                }
+            }.start();
+        } else {
+            return addAccountAsUser(accountType, authTokenType, requiredFeatures, addAccountOptions,
+                    activity, callback, handler, mContext.getUser());
+        }
+    }
+
+    /**
+     * @see #addAccount(String, String, String[], Bundle, Activity, AccountManagerCallback, Handler)
+     * @hide
+     */
+    public AccountManagerFuture<Bundle> addAccountAsUser(final String accountType,
+            final String authTokenType, final String[] requiredFeatures,
+            final Bundle addAccountOptions, final Activity activity,
+            AccountManagerCallback<Bundle> callback, Handler handler, final UserHandle userHandle) {
+        if (accountType == null) throw new IllegalArgumentException("accountType is null");
+        if (userHandle == null) throw new IllegalArgumentException("userHandle is null");
+        final Bundle optionsIn = new Bundle();
+        if (addAccountOptions != null) {
+            optionsIn.putAll(addAccountOptions);
+        }
+        optionsIn.putString(KEY_ANDROID_PACKAGE_NAME, mContext.getPackageName());
+
+        return new AmsTask(activity, handler, callback) {
+            @Override
+            public void doWork() throws RemoteException {
+                mService.addAccountAsUser(mResponse, accountType, authTokenType,
+                        requiredFeatures, activity != null, optionsIn, userHandle.getIdentifier());
+            }
+        }.start();
+    }
+
+
+    /**
+     * Adds shared accounts from a parent user to a secondary user. Adding the shared account
+     * doesn't take effect immediately. When the target user starts up, any pending shared accounts
+     * are attempted to be copied to the target user from the primary via calls to the
+     * authenticator.
+     * @param parentUser parent user
+     * @param user target user
+     * @hide
+     */
+    public void addSharedAccountsFromParentUser(UserHandle parentUser, UserHandle user) {
+        try {
+            mService.addSharedAccountsFromParentUser(parentUser.getIdentifier(),
+                    user.getIdentifier(), mContext.getOpPackageName());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Copies an account from one user to another user.
+     * @param account the account to copy
+     * @param fromUser the user to copy the account from
+     * @param toUser the target user
+     * @param callback Callback to invoke when the request completes,
+     *     null for no callback
+     * @param handler {@link Handler} identifying the callback thread,
+     *     null for the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Boolean indicated 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();
+    }
+
+    /**
+     * Confirms that the user knows the password for an account to make extra
+     * sure they are the owner of the account.  The user-entered password can
+     * be supplied directly, otherwise the authenticator for this account type
+     * prompts the user with the appropriate interface.  This method is
+     * intended for applications which want extra assurance; for example, the
+     * phone lock screen uses this to let the user unlock the phone with an
+     * account password if they forget the lock pattern.
+     *
+     * <p>If the user-entered password matches a saved password for this
+     * account, the request is considered valid; otherwise the authenticator
+     * verifies the password (usually by contacting the server).
+     *
+     * <p>This method may be called from any thread, but the returned
+     * {@link AccountManagerFuture} must not be used on the main thread.
+     *
+     * <p><b>NOTE:</b> If targeting your app to work on API level 22 and before,
+     * MANAGE_ACCOUNTS permission is needed for those platforms. See docs
+     * for this function in API level 22.
+     *
+     * @param account The account to confirm password knowledge for
+     * @param options Authenticator-specific options for the request;
+     *     if the {@link #KEY_PASSWORD} string field is present, the
+     *     authenticator may use it directly rather than prompting the user;
+     *     may be null or empty
+     * @param activity The {@link Activity} context to use for launching a new
+     *     authenticator-defined sub-Activity to prompt the user to enter a
+     *     password; used only to call startActivity(); if null, the prompt
+     *     will not be launched directly, but the necessary {@link Intent}
+     *     will be returned to the caller instead
+     * @param callback Callback to invoke when the request completes,
+     *     null for no callback
+     * @param handler {@link Handler} identifying the callback thread,
+     *     null for the main thread
+     * @return An {@link AccountManagerFuture} which resolves to a Bundle
+     *     with these fields if activity or password was supplied and
+     *     the account was successfully verified:
+     * <ul>
+     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account verified
+     * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
+     * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success
+     * </ul>
+     *
+     * If no activity or password was specified, the returned Bundle contains
+     * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+     * password prompt.
+     *
+     * <p>Also the returning Bundle may contain {@link
+     * #KEY_LAST_AUTHENTICATED_TIME} indicating the last time the
+     * credential was validated/created.
+     *
+     * If an error occurred,{@link AccountManagerFuture#getResult()} throws:
+     * <ul>
+     * <li> {@link AuthenticatorException} if the authenticator failed to respond
+     * <li> {@link OperationCanceledException} if the operation was canceled for
+     *      any reason, including the user canceling the password prompt
+     * <li> {@link IOException} if the authenticator experienced an I/O problem
+     *      verifying the password, usually because of network trouble
+     * </ul>
+     */
+    @UserHandleAware
+    public AccountManagerFuture<Bundle> confirmCredentials(final Account account,
+            final Bundle options,
+            final Activity activity,
+            final AccountManagerCallback<Bundle> callback,
+            final Handler handler) {
+        return confirmCredentialsAsUser(account, options, activity, callback, handler,
+                mContext.getUser());
+    }
+
+    /**
+     * @hide
+     * Same as {@link #confirmCredentials(Account, Bundle, Activity, AccountManagerCallback, Handler)}
+     * but for the specified user.
+     */
+    @UnsupportedAppUsage
+    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
+     */
+    @UserHandleAware
+    public AccountManagerFuture<Bundle> finishSession(
+            final Bundle sessionBundle,
+            final Activity activity,
+            AccountManagerCallback<Bundle> callback,
+            Handler handler) {
+        return finishSessionAsUser(
+                sessionBundle,
+                activity,
+                mContext.getUser(),
+                callback,
+                handler);
+    }
+
+    /**
+     * @see #finishSession
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(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..b7bf11d
--- /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.compat.annotation.UnsupportedAppUsage;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * A {@link Parcelable} value type that contains information about an account authenticator.
+ */
+public class AuthenticatorDescription implements Parcelable {
+    /** The string that uniquely identifies an authenticator */
+    final public String type;
+
+    /** A resource id of a label for the authenticator that is suitable for displaying */
+    final public int labelId;
+
+    /** A resource id of a icon for the authenticator */
+    final public int iconId;
+
+    /** A resource id of a smaller icon for the authenticator */
+    final public int smallIconId;
+
+    /**
+     * A resource id for a hierarchy of PreferenceScreen to be added to the settings page for the
+     * account. See {@link AbstractAccountAuthenticator} for an example.
+     */
+    final public int accountPreferencesId;
+
+    /** The package name that can be used to lookup the resources from above. */
+    final public String packageName;
+
+    /** Authenticator handles its own token caching and permission screen */
+    final public boolean customTokens;
+
+    /** A constructor for a full AuthenticatorDescription */
+    public AuthenticatorDescription(String type, String packageName, int labelId, int iconId,
+            int smallIconId, int prefId, boolean customTokens) {
+        if (type == null) throw new IllegalArgumentException("type cannot be null");
+        if (packageName == null) throw new IllegalArgumentException("packageName cannot be null");
+        this.type = type;
+        this.packageName = packageName;
+        this.labelId = labelId;
+        this.iconId = iconId;
+        this.smallIconId = smallIconId;
+        this.accountPreferencesId = prefId;
+        this.customTokens = customTokens;
+    }
+
+    public AuthenticatorDescription(String type, String packageName, int labelId, int iconId,
+            int smallIconId, int prefId) {
+        this(type, packageName, labelId, iconId, smallIconId, prefId, false);
+    }
+
+    /**
+     * A factory method for creating an AuthenticatorDescription that can be used as a key
+     * to identify the authenticator by its type.
+     */
+
+    public static AuthenticatorDescription newKey(String type) {
+        if (type == null) throw new IllegalArgumentException("type cannot be null");
+        return new AuthenticatorDescription(type);
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private AuthenticatorDescription(String type) {
+        this.type = type;
+        this.packageName = null;
+        this.labelId = 0;
+        this.iconId = 0;
+        this.smallIconId = 0;
+        this.accountPreferencesId = 0;
+        this.customTokens = false;
+    }
+
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    private AuthenticatorDescription(Parcel source) {
+        this.type = source.readString();
+        this.packageName = source.readString();
+        this.labelId = source.readInt();
+        this.iconId = source.readInt();
+        this.smallIconId = source.readInt();
+        this.accountPreferencesId = source.readInt();
+        this.customTokens = source.readByte() == 1;
+    }
+
+    /** @inheritDoc */
+    public int describeContents() {
+        return 0;
+    }
+
+    /** Returns the hashcode of the type only. */
+    public int hashCode() {
+        return type.hashCode();
+    }
+
+    /** Compares the type only, suitable for key comparisons. */
+    public boolean equals(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..3cdd691
--- /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.compat.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) {